From 908ea361264a85e8feda127133023996cce75bb3 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 9 May 2025 23:23:37 +0800 Subject: [PATCH 001/142] ui(raylib): add thread-safe property access to WifiManagerWrapper (#35162) * Add thread-safe property access to WifiManagerWrapper * cleanup * type-safe manager, always specify default * import --------- Co-authored-by: Cameron Clough --- system/ui/lib/wifi_manager.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index af533e9a2a..990e1d14a1 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -1,10 +1,12 @@ import asyncio +import concurrent.futures import threading import time import uuid from collections.abc import Callable from dataclasses import dataclass from enum import IntEnum +from typing import TypeVar from dbus_next.aio import MessageBus from dbus_next import BusType, Variant, Message @@ -12,6 +14,8 @@ from dbus_next.errors import DBusError from dbus_next.constants import MessageType from openpilot.common.swaglog import cloudlog +T = TypeVar("T") + # NetworkManager constants NM = "org.freedesktop.NetworkManager" NM_PATH = '/org/freedesktop/NetworkManager' @@ -437,11 +441,11 @@ class WifiManagerWrapper: @property def networks(self) -> list[NetworkInfo]: """Get the current list of networks.""" - return self._manager.networks if self._manager else [] + return self._run_coroutine_sync(lambda manager: manager.networks.copy(), default=[]) def is_saved(self, ssid: str) -> bool: """Check if a network is saved.""" - return self._manager.is_saved(ssid) if self._manager else False + return self._run_coroutine_sync(lambda manager: manager.is_saved(ssid), default=False) def connect(self): """Connect to DBus and start Wi-Fi scanning.""" @@ -479,3 +483,22 @@ class WifiManagerWrapper: cloudlog.error("WifiManager thread is not running") return asyncio.run_coroutine_threadsafe(coro, self._loop) + + def _run_coroutine_sync(self, func: Callable[[WifiManager], T], default: T) -> T: + """Run a function synchronously in the async thread.""" + if not self._running or not self._loop or not self._manager: + return default + future = concurrent.futures.Future[T]() + + def wrapper(manager: WifiManager) -> None: + try: + future.set_result(func(manager)) + except Exception as e: + future.set_exception(e) + + try: + self._loop.call_soon_threadsafe(wrapper, self._manager) + return future.result(timeout=1.0) + except Exception as e: + cloudlog.error(f"WifiManagerWrapper property access failed: {e}") + return default From 1f312e16d492473a4d252c603f04ec7aff71cfd4 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 9 May 2025 15:13:52 -0400 Subject: [PATCH 002/142] messaging: cleanup zero-frequency service initialization (#35145) * messaging: fix bug with relaxed checks under simulation * refactor * cleanup * fix bug * Revert "fix bug" This reverts commit ea31f3ee83676c5b8ffe57500881557f7586998c. * that did need to be different * fix bug, add test coverage * retry CI --- cereal/messaging/__init__.py | 26 +++++++++---------- cereal/messaging/tests/test_pub_sub_master.py | 6 ++++- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index 8ad956b61b..b03285f80a 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -145,12 +145,16 @@ class SubMaster: self.updated = {s: False for s in services} self.recv_time = {s: 0. for s in services} self.recv_frame = {s: 0 for s in services} - self.alive = {s: False for s in services} - self.freq_ok = {s: False for s in services} self.sock = {} self.data = {} - self.valid = {} - self.logMonoTime = {} + self.logMonoTime = {s: 0 for s in services} + + # zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks + on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services} + self.static_freq_services = set(s for s in services if not on_demand[s]) + self.alive = {s: on_demand[s] for s in services} + self.freq_ok = {s: on_demand[s] for s in services} + self.valid = {s: on_demand[s] for s in services} self.freq_tracker: Dict[str, FrequencyTracker] = {} self.poller = Poller() @@ -177,8 +181,6 @@ class SubMaster: data = new_message(s, 0) # lists self.data[s] = getattr(data.as_reader(), s) - self.logMonoTime[s] = 0 - self.valid[s] = False self.freq_tracker[s] = FrequencyTracker(SERVICE_LIST[s].frequency, self.update_freq, s == poll) def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader: @@ -215,14 +217,10 @@ class SubMaster: self.logMonoTime[s] = msg.logMonoTime self.valid[s] = msg.valid - for s in self.services: - if SERVICE_LIST[s].frequency > 1e-5 and not self.simulation: - # alive if delay is within 10x the expected frequency - self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) - self.freq_ok[s] = self.freq_tracker[s].valid - else: - self.freq_ok[s] = True - self.alive[s] = self.seen[s] if self.simulation else True + for s in self.static_freq_services: + # alive if delay is within 10x the expected frequency; checks relaxed in simulator + self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation) + self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation def all_alive(self, service_list: Optional[List[str]] = None) -> bool: return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive) diff --git a/cereal/messaging/tests/test_pub_sub_master.py b/cereal/messaging/tests/test_pub_sub_master.py index e9bc7a85cb..e47e713393 100644 --- a/cereal/messaging/tests/test_pub_sub_master.py +++ b/cereal/messaging/tests/test_pub_sub_master.py @@ -6,6 +6,7 @@ import cereal.messaging as messaging from cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \ random_bytes, random_carstate, assert_carstate, \ zmq_sleep +from cereal.services import SERVICE_LIST class TestSubMaster: @@ -26,7 +27,9 @@ class TestSubMaster: sm = messaging.SubMaster(socks) assert sm.frame == -1 assert not any(sm.updated.values()) - assert not any(sm.alive.values()) + assert not any(sm.seen.values()) + on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in sm.services} + assert all(sm.alive[s] == sm.valid[s] == sm.freq_ok[s] == on_demand[s] for s in sm.services) assert all(t == 0. for t in sm.recv_time.values()) assert all(f == 0 for f in sm.recv_frame.values()) assert all(t == 0 for t in sm.logMonoTime.values()) @@ -83,6 +86,7 @@ class TestSubMaster: "cameraOdometry": (20, 10), "liveCalibration": (4, 4), "carParams": (None, None), + "userFlag": (None, None), } for service, (max_freq, min_freq) in checks.items(): From 32167e02c0f9a3d6910061d453bfb0c5a63add2d Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 9 May 2025 14:57:00 -0500 Subject: [PATCH 003/142] ui: show alert when user flag (bookmark) pressed (#34920) * feat: Show alert on user flag event * feat: Temporarily update border status on user flag to match connect timeline * Revert "feat: Temporarily update border status on user flag to match connect timeline" This reverts commit f1da6a4f5f555d4db50b47fb68ce74ae7737b8af. * feat: Increase alert duration to 1.5 seconds * remove audible alert for bookmark event * refactor: Use NormalPermamentAlert for user flag alert * fix: Update userFlag enum value in OnroadEvent struct * fix: Handle userFlag event even in dashcam mode * don't need to ignore that anymore * remove 'userFlag' from ignore list --------- Co-authored-by: Jason Young --- cereal/log.capnp | 1 + selfdrive/selfdrived/events.py | 5 ++++- selfdrive/selfdrived/selfdrived.py | 10 +++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 6b5398405c..5c5ceb5635 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -127,6 +127,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { espActive @90; personalityChanged @91; aeb @92; + userFlag @95; soundsUnavailableDEPRECATED @47; } diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index c187ed474a..6293deb2af 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -894,7 +894,7 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # causing the connection to the panda to be lost EventName.usbError: { ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"), - ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device", ""), + ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device"), ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"), }, @@ -982,6 +982,9 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { ET.WARNING: personality_changed_alert, }, + EventName.userFlag: { + ET.PERMANENT: NormalPermanentAlert("Bookmark Saved", duration=1.5), + }, } diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index face7dab29..c888551777 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -77,7 +77,7 @@ class SelfdriveD: self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', - 'controlsState', 'carControl', 'driverAssistance', 'alertDebug'] + \ + 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userFlag'] + \ self.camera_packets + self.sensor_packets + self.gps_packets, ignore_alive=ignore, ignore_avg_freq=ignore, ignore_valid=ignore, frequency=int(1/DT_CTRL)) @@ -159,7 +159,11 @@ class SelfdriveD: self.events.add(EventName.selfdriveInitializing) return - # no more events while in dashcam mode + # Check for user flag (bookmark) press + if self.sm.updated['userFlag']: + self.events.add(EventName.userFlag) + + # Don't add any more events while in dashcam mode if self.CP.passive: return @@ -365,7 +369,7 @@ class SelfdriveD: if self.sm['modelV2'].frameDropPerc > 20: self.events.add(EventName.modeldLagging) - # decrement personality on distance button press + # Decrement personality on distance button press if self.CP.openpilotLongitudinalControl: if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents): self.personality = (self.personality - 1) % 3 From db855d56d307d0eb935e96c87254771955ed86b0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 9 May 2025 15:18:52 -0700 Subject: [PATCH 004/142] Log Hexagon DSP temperature (#35166) * add hexagon dsp temp * log * whoops --- cereal/log.capnp | 1 + system/hardware/base.py | 1 + system/hardware/tici/hardware.py | 1 + 3 files changed, 3 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index 5c5ceb5635..9ab51e0b77 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -490,6 +490,7 @@ struct DeviceState @0xa4d8b5af2aa492eb { # device thermals cpuTempC @26 :List(Float32); gpuTempC @27 :List(Float32); + dspTempC @49 :Float32; memoryTempC @28 :Float32; nvmeTempC @35 :List(Float32); modemTempC @36 :List(Float32); diff --git a/system/hardware/base.py b/system/hardware/base.py index 1e3b94e44e..90b42b2f1f 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -33,6 +33,7 @@ class ThermalZone: class ThermalConfig: cpu: list[ThermalZone] | None = None gpu: list[ThermalZone] | None = None + dsp: ThermalZone | None = None pmic: list[ThermalZone] | None = None memory: ThermalZone | None = None intake: ThermalZone | None = None diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index ffa852403f..694d42f4d6 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -335,6 +335,7 @@ class Tici(HardwareBase): return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] + [ThermalZone(f"cpu{i}-gold-usr") for i in range(4)], gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")], + dsp=ThermalZone("compute-hvx-usr"), memory=ThermalZone("ddr-usr"), pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")], intake=intake, From 13965bdf0bda5ad1ef0b7faa6011bc8e3274fc17 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Fri, 9 May 2025 19:59:44 -0700 Subject: [PATCH 005/142] docs: clarify process to connect to 3/3X over ADB (#35090) * three * codespell being smart with me --------- Co-authored-by: Maxime Desroches --- docs/assets/three-back.svg | 3 +++ docs/how-to/connect-to-comma.md | 6 +++++- pyproject.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 docs/assets/three-back.svg diff --git a/docs/assets/three-back.svg b/docs/assets/three-back.svg new file mode 100644 index 0000000000..4dfeeeb46a --- /dev/null +++ b/docs/assets/three-back.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a5245f9458982b608fee67fc689d899ca638a405ff62bf9db5e4978b177ef3e +size 121394 diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index 469ef81672..cbaccaae6a 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -32,9 +32,13 @@ For doing development work on device, it's recommended to use [SSH agent forward ## ADB -In order to use ADB on your device, you'll need to enable it in the device's settings. +In order to use ADB on your device, you'll need to perform the following steps using the image below for reference: +![comma 3/3x back](../assets/three-back.svg) + +* Plug your device into constant power using port 2, letting the device boot up * Enable ADB in your device's settings +* Plug in your device to your PC using port 1 * Connect to your device * `adb shell` over USB * `adb connect` over WiFi diff --git a/pyproject.toml b/pyproject.toml index bf43840d71..20bfebd938 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -171,7 +171,7 @@ quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" -skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*" +skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*" [tool.mypy] python_version = "3.11" From 3b94e6f92f922135d05d3d72c150f5d3b65819b0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 00:59:42 +0800 Subject: [PATCH 006/142] system/ui: add tethering support to WifiManager (#35167) add tethering support to WifiManager --- system/ui/lib/wifi_manager.py | 160 ++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 990e1d14a1..ff18092364 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -12,6 +12,7 @@ from dbus_next.aio import MessageBus from dbus_next import BusType, Variant, Message from dbus_next.errors import DBusError from dbus_next.constants import MessageType +from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog T = TypeVar("T") @@ -29,6 +30,9 @@ NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device" NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 +TETHERING_IP_ADDRESS = "192.168.43.1" +DEFAULT_TETHERING_PASSWORD = "12345678" + # NetworkManager device states class NMDeviceState(IntEnum): DISCONNECTED = 30 @@ -72,6 +76,7 @@ class WifiManager: self.saved_connections: dict[str, str] = {} self.active_ap_path: str = "" self.scan_task: asyncio.Task | None = None + self._tethering_ssid = "weedle-" + Params().get("DongleId", encoding="utf-8") self.running: bool = True async def connect(self) -> None: @@ -83,6 +88,7 @@ class WifiManager: await self._setup_signals(self.device_path) self.active_ap_path = await self.get_active_access_point() + await self.add_tethering_connection(self._tethering_ssid, DEFAULT_TETHERING_PASSWORD) self.saved_connections = await self._get_saved_connections() self.scan_task = asyncio.create_task(self._periodic_scan()) except DBusError as e: @@ -199,6 +205,160 @@ class WifiManager: return False + async def add_tethering_connection(self, ssid: str, password: str = "12345678") -> bool: + """Create a WiFi tethering connection.""" + if len(password) < 8: + print("Tethering password must be at least 8 characters") + return False + + try: + # First, check if a hotspot connection already exists + settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE) + connection_paths = await settings_iface.call_list_connections() + + # Look for an existing hotspot connection + for path in connection_paths: + try: + settings = await self._get_connection_settings(path) + conn_type = settings.get('connection', {}).get('type', Variant('s', '')).value + wifi_mode = settings.get('802-11-wireless', {}).get('mode', Variant('s', '')).value + + if conn_type == '802-11-wireless' and wifi_mode == 'ap': + # Extract the SSID to check + connection_ssid = self._extract_ssid(settings) + if connection_ssid == ssid: + return True + except DBusError: + continue + + connection = { + 'connection': { + 'id': Variant('s', 'Hotspot'), + 'uuid': Variant('s', str(uuid.uuid4())), + 'type': Variant('s', '802-11-wireless'), + 'interface-name': Variant('s', 'wlan0'), + 'autoconnect': Variant('b', False), + }, + '802-11-wireless': { + 'band': Variant('s', 'bg'), + 'mode': Variant('s', 'ap'), + 'ssid': Variant('ay', ssid.encode('utf-8')), + }, + '802-11-wireless-security': { + 'group': Variant('as', ['ccmp']), + 'key-mgmt': Variant('s', 'wpa-psk'), + 'pairwise': Variant('as', ['ccmp']), + 'proto': Variant('as', ['rsn']), + 'psk': Variant('s', password), + }, + 'ipv4': { + 'method': Variant('s', 'shared'), + 'address-data': Variant('aa{sv}', [{'address': Variant('s', TETHERING_IP_ADDRESS), 'prefix': Variant('u', 24)}]), + 'gateway': Variant('s', TETHERING_IP_ADDRESS), + 'never-default': Variant('b', True), + }, + 'ipv6': { + 'method': Variant('s', 'ignore'), + }, + } + + settings_iface = await self._get_interface(NM, NM_SETTINGS_PATH, NM_SETTINGS_IFACE) + new_connection = await settings_iface.call_add_connection(connection) + print(f"Added tethering connection with path: {new_connection}") + return True + except DBusError as e: + print(f"Failed to add tethering connection: {e}") + return False + except Exception as e: + print(f"Unexpected error adding tethering connection: {e}") + return False + + async def get_tethering_password(self) -> str: + """Get the current tethering password.""" + try: + hotspot_path = self.saved_connections.get(self._tethering_ssid) + if hotspot_path: + conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE) + secrets = await conn_iface.call_get_secrets('802-11-wireless-security') + if secrets and '802-11-wireless-security' in secrets: + psk = secrets.get('802-11-wireless-security', {}).get('psk', Variant('s', '')).value + return str(psk) if psk is not None else "" + return "" + except DBusError as e: + print(f"Failed to get tethering password: {e}") + return "" + except Exception as e: + print(f"Unexpected error getting tethering password: {e}") + return "" + + async def set_tethering_password(self, password: str) -> bool: + """Set the tethering password.""" + if len(password) < 8: + cloudlog.error("Tethering password must be at least 8 characters") + return False + + try: + hotspot_path = self.saved_connections.get(self._tethering_ssid) + if not hotspot_path: + print("No hotspot connection found") + return False + + # Update the connection settings with new password + settings = await self._get_connection_settings(hotspot_path) + if '802-11-wireless-security' not in settings: + settings['802-11-wireless-security'] = {} + settings['802-11-wireless-security']['psk'] = Variant('s', password) + + # Apply changes + conn_iface = await self._get_interface(NM, hotspot_path, NM_CONNECTION_IFACE) + await conn_iface.call_update(settings) + + # Check if connection is active and restart if needed + is_active = False + nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE) + active_connections = await nm_iface.get_active_connections() + + for conn_path in active_connections: + props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE) + conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection') + if conn_id_path.value == hotspot_path: + is_active = True + await nm_iface.call_deactivate_connection(conn_path) + break + + if is_active: + await nm_iface.call_activate_connection(hotspot_path, self.device_path, "/") + + print("Tethering password updated successfully") + return True + except DBusError as e: + print(f"Failed to set tethering password: {e}") + return False + except Exception as e: + print(f"Unexpected error setting tethering password: {e}") + return False + + async def is_tethering_active(self) -> bool: + """Check if tethering is active for the specified SSID.""" + try: + hotspot_path = self.saved_connections.get(self._tethering_ssid) + if not hotspot_path: + return False + + nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE) + active_connections = await nm_iface.get_active_connections() + + for conn_path in active_connections: + props_iface = await self._get_interface(NM, conn_path, NM_PROPERTIES_IFACE) + conn_id_path = await props_iface.call_get('org.freedesktop.NetworkManager.Connection.Active', 'Connection') + + if conn_id_path.value == hotspot_path: + return True + + return False + except Exception: + return False + async def _periodic_scan(self): while self.running: try: From 7147c2695447492a6b456674a4dd1b30b344084e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 01:00:28 +0800 Subject: [PATCH 007/142] system/ui: fix WIFI authentication callback and connection tracking (#35169) fix authentication callback and state handling --- system/ui/lib/wifi_manager.py | 22 +++++++++++++++++++--- system/ui/widgets/network.py | 11 ++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index ff18092364..d3d9bb4414 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -61,7 +61,7 @@ class NetworkInfo: @dataclass class WifiManagerCallbacks: - need_auth: Callable[[], None] | None = None + need_auth: Callable[[str], None] | None = None activated: Callable[[], None] | None = None forgotten: Callable[[], None] | None = None @@ -78,6 +78,7 @@ class WifiManager: self.scan_task: asyncio.Task | None = None self._tethering_ssid = "weedle-" + Params().get("DongleId", encoding="utf-8") self.running: bool = True + self._current_connection_ssid: str | None = None async def connect(self) -> None: """Connect to the DBus system bus.""" @@ -133,6 +134,9 @@ class WifiManager: try: nm_iface = await self._get_interface(NM, path, NM_CONNECTION_IFACE) await nm_iface.call_delete() + if self._current_connection_ssid == ssid: + self._current_connection_ssid = None + del self.saved_connections[ssid] return True except DBusError as e: cloudlog.error(f"Failed to delete connection for SSID: {ssid}. Error: {e}") @@ -153,6 +157,7 @@ class WifiManager: async def connect_to_network(self, ssid: str, password: str = None, bssid: str = None, is_hidden: bool = False) -> None: """Connect to a selected Wi-Fi network.""" try: + self._current_connection_ssid = ssid connection = { 'connection': { 'type': Variant('s', '802-11-wireless'), @@ -182,8 +187,8 @@ class WifiManager: nm_iface = await self._get_interface(NM, NM_PATH, NM_IFACE) await nm_iface.call_add_and_activate_connection(connection, self.device_path, "/") await self._update_connection_status() - except DBusError as e: + self._current_connection_ssid = None cloudlog.error(f"Error connecting to network: {e}") def is_saved(self, ssid: str) -> bool: @@ -403,11 +408,22 @@ class WifiManager: if self.callbacks.activated: self.callbacks.activated() asyncio.create_task(self._update_connection_status()) + self._current_connection_ssid = None elif new_state in (NMDeviceState.DISCONNECTED, NMDeviceState.NEED_AUTH): for network in self.networks: network.is_connected = False if new_state == NMDeviceState.NEED_AUTH and reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and self.callbacks.need_auth: - self.callbacks.need_auth() + if self._current_connection_ssid: + self.callbacks.need_auth(self._current_connection_ssid) + else: + # Try to find the network from active_ap_path + for network in self.networks: + if network.path == self.active_ap_path: + self.callbacks.need_auth(network.ssid) + break + else: + # Couldn't identify the network that needs auth + cloudlog.error("Network needs authentication but couldn't identify which one") def _on_new_connection(self, path: str) -> None: """Callback for NewConnection signal.""" diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 59427b57b8..0e9ad349b6 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -142,10 +142,15 @@ class WifiManagerUI: self.state = StateForgetting(network) self.wifi_manager.forget_connection(network.ssid) - def _on_need_auth(self): + def _on_need_auth(self, ssid): match self.state: - case StateConnecting(network): - self.state = StateNeedsAuth(network) + case StateConnecting(ssid): + self.state = StateNeedsAuth(ssid) + case _: + # Find network by SSID + network = next((n for n in self.wifi_manager.networks if n.ssid == ssid), None) + if network: + self.state = StateNeedsAuth(network) def _on_activated(self): if isinstance(self.state, StateConnecting): From eaa595958c15f958cb248fb9287672e201cbd369 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 05:22:06 +0800 Subject: [PATCH 008/142] system/ui: prevent duplicate WiFi connections when retrying with new password (#35174) Prevent duplicate WiFi connections when retrying with new password --- system/ui/lib/wifi_manager.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index d3d9bb4414..29d3231d5f 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -136,7 +136,10 @@ class WifiManager: await nm_iface.call_delete() if self._current_connection_ssid == ssid: self._current_connection_ssid = None - del self.saved_connections[ssid] + + if ssid in self.saved_connections: + del self.saved_connections[ssid] + return True except DBusError as e: cloudlog.error(f"Failed to delete connection for SSID: {ssid}. Error: {e}") @@ -158,6 +161,17 @@ class WifiManager: """Connect to a selected Wi-Fi network.""" try: self._current_connection_ssid = ssid + + if ssid in self.saved_connections: + # Forget old connection if new password provided + if password: + await self.forget_connection(ssid) + await asyncio.sleep(0.2) # NetworkManager delay + else: + # Just activate existing connection + await self.activate_connection(ssid) + return + connection = { 'connection': { 'type': Variant('s', '802-11-wireless'), From f1760e63d39d1be2ed470364e435b2cd981c8e45 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 15:26:25 +0800 Subject: [PATCH 009/142] system/ui: performance optimizations for WiFi Manager with cached network data (#35170) Performance optimizations for WiFi Manager with cached network data --- system/ui/lib/wifi_manager.py | 15 ++++++++++----- system/ui/widgets/network.py | 22 ++++++++++++++-------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 29d3231d5f..00649b146b 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -1,5 +1,6 @@ import asyncio import concurrent.futures +import copy import threading import time import uuid @@ -56,6 +57,7 @@ class NetworkInfo: security_type: SecurityType path: str bssid: str + is_saved: bool = False # saved_path: str @@ -64,6 +66,7 @@ class WifiManagerCallbacks: need_auth: Callable[[str], None] | None = None activated: Callable[[], None] | None = None forgotten: Callable[[], None] | None = None + networks_updated: Callable[[list[NetworkInfo]], None] | None = None class WifiManager: @@ -452,6 +455,8 @@ class WifiManager: del self.saved_connections[ssid] if self.callbacks.forgotten: self.callbacks.forgotten() + # Update network list to reflect the removed saved connection + asyncio.create_task(self._update_connection_status()) break async def _add_saved_connection(self, path: str) -> None: @@ -460,6 +465,7 @@ class WifiManager: settings = await self._get_connection_settings(path) if ssid := self._extract_ssid(settings): self.saved_connections[ssid] = path + await self._update_connection_status() except DBusError as e: cloudlog.error(f"Failed to add connection {path}: {e}") @@ -517,6 +523,7 @@ class WifiManager: path=ap_path, bssid=bssid, is_connected=self.active_ap_path == ap_path, + is_saved=ssid in self.saved_connections ) except DBusError as e: @@ -533,6 +540,9 @@ class WifiManager: ), ) + if self.callbacks.networks_updated: + self.callbacks.networks_updated(copy.deepcopy(self.networks)) + async def _get_connection_settings(self, path): """Fetch connection settings for a specific connection path.""" try: @@ -628,11 +638,6 @@ class WifiManagerWrapper: self._thread.join(timeout=2.0) self._running = False - @property - def networks(self) -> list[NetworkInfo]: - """Get the current list of networks.""" - return self._run_coroutine_sync(lambda manager: manager.networks.copy(), default=[]) - def is_saved(self, ssid: str) -> bool: """Check if a network is saved.""" return self._run_coroutine_sync(lambda manager: manager.is_saved(ssid), default=False) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 0e9ad349b6..14f4017925 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -48,13 +48,15 @@ class WifiManagerUI: self.scroll_panel = GuiScrollPanel() self.keyboard = Keyboard() + self._networks: list[NetworkInfo] = [] + self.wifi_manager = wifi_manager - self.wifi_manager.set_callbacks(WifiManagerCallbacks(self._on_need_auth, self._on_activated, self._on_forgotten)) + self.wifi_manager.set_callbacks(WifiManagerCallbacks(self._on_need_auth, self._on_activated, self._on_forgotten, self._on_network_updated)) self.wifi_manager.start() self.wifi_manager.connect() def render(self, rect: rl.Rectangle): - if not self.wifi_manager.networks: + if not self._networks: gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) return @@ -77,19 +79,19 @@ class WifiManagerUI: self._draw_network_list(rect) def _draw_network_list(self, rect: rl.Rectangle): - content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self.wifi_manager.networks) * ITEM_HEIGHT) + content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT) offset = self.scroll_panel.handle_scroll(rect, content_rect) clicked = self.scroll_panel.is_click_valid() rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - for i, network in enumerate(self.wifi_manager.networks): + for i, network in enumerate(self._networks): y_offset = rect.y + i * ITEM_HEIGHT + offset.y item_rect = rl.Rectangle(rect.x, y_offset, rect.width, ITEM_HEIGHT) if not rl.check_collision_recs(item_rect, rect): continue self._draw_network_item(item_rect, network, clicked) - if i < len(self.wifi_manager.networks) - 1: + if i < len(self._networks) - 1: line_y = int(item_rect.y + item_rect.height - 1) rl.draw_line(int(item_rect.x), int(line_y), int(item_rect.x + item_rect.width), line_y, rl.LIGHTGRAY) @@ -115,7 +117,7 @@ class WifiManagerUI: rl.gui_label(state_rect, status_text) # If the network is saved, show the "Forget" button - if self.wifi_manager.is_saved(network.ssid): + if network.is_saved: forget_btn_rect = rl.Rectangle( rect.x + rect.width - self.btn_width, rect.y + (ITEM_HEIGHT - 80) / 2, @@ -126,22 +128,26 @@ class WifiManagerUI: self.state = StateShowForgetConfirm(network) if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), label_rect) and clicked: - if not self.wifi_manager.is_saved(network.ssid): + if not network.is_saved: self.state = StateNeedsAuth(network) else: self.connect_to_network(network) def connect_to_network(self, network: NetworkInfo, password=''): self.state = StateConnecting(network) - if self.wifi_manager.is_saved(network.ssid) and not password: + if network.is_saved and not password: self.wifi_manager.activate_connection(network.ssid) else: self.wifi_manager.connect_to_network(network.ssid, password) def forget_network(self, network: NetworkInfo): self.state = StateForgetting(network) + network.is_saved = False self.wifi_manager.forget_connection(network.ssid) + def _on_network_updated(self, networks: list[NetworkInfo]): + self._networks = networks + def _on_need_auth(self, ssid): match self.state: case StateConnecting(ssid): From bbeb37d7266a98ba88abda27e869e8178a41aafb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 15:43:23 +0800 Subject: [PATCH 010/142] system/ui: add MultiOptionDialog for selection from scrollable lists (#35176) * add MultiOptionDialog for selection from scrollable lists * mv --------- Co-authored-by: Cameron Clough --- system/ui/lib/button.py | 21 ++++++-- system/ui/widgets/option_dialog.py | 81 ++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 system/ui/widgets/option_dialog.py diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 034189275f..1857fd21cc 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -10,6 +10,12 @@ class ButtonStyle(IntEnum): TRANSPARENT = 3 # For buttons with transparent background and border +class TextAlignment(IntEnum): + LEFT = 0 + CENTER = 1 + RIGHT = 2 + + DEFAULT_BUTTON_FONT_SIZE = 60 BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255) BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51) @@ -38,6 +44,8 @@ def gui_button( button_style: ButtonStyle = ButtonStyle.NORMAL, is_enabled: bool = True, border_radius: int = 10, # Corner rounding in pixels + text_alignment: TextAlignment = TextAlignment.CENTER, + text_padding: int = 20, # Padding for left/right alignment ) -> int: result = 0 @@ -58,11 +66,16 @@ def gui_button( rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE) font = gui_app.font(font_weight) - # Center text in the button text_size = rl.measure_text_ex(font, text, font_size, 0) - text_pos = rl.Vector2( - rect.x + (rect.width - text_size.x) // 2, rect.y + (rect.height - text_size.y) // 2 - ) + text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering + + # Horizontal alignment + if text_alignment == TextAlignment.LEFT: + text_pos.x = rect.x + text_padding + elif text_alignment == TextAlignment.CENTER: + text_pos.x = rect.x + (rect.width - text_size.x) // 2 + elif text_alignment == TextAlignment.RIGHT: + text_pos.x = rect.x + rect.width - text_size.x - text_padding # Draw the button text text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py new file mode 100644 index 0000000000..c25c5c6e6c --- /dev/null +++ b/system/ui/widgets/option_dialog.py @@ -0,0 +1,81 @@ +import pyray as rl + +from openpilot.system.ui.lib.button import gui_button, ButtonStyle, TextAlignment +from openpilot.system.ui.lib.label import gui_label +from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel + + +class MultiOptionDialog: + def __init__(self, title, options, current=""): + self._title = title + self._options = options + self._current = current if current in options else "" + self._selection = self._current + self._option_height = 80 + self._padding = 20 + self.scroll_panel = GuiScrollPanel() + + @property + def selection(self): + return self._selection + + def render(self, rect): + title_rect = rl.Rectangle(rect.x + self._padding, rect.y + self._padding, rect.width - 2 * self._padding, 70) + gui_label(title_rect, self._title, 70) + + options_y_start = rect.y + 120 + options_height = len(self._options) * (self._option_height + 10) + options_rect = rl.Rectangle(rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, options_height) + + view_rect = rl.Rectangle( + rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, rect.height - 200 - 2 * self._padding + ) + + offset = self.scroll_panel.handle_scroll(view_rect, options_rect) + is_click_valid = self.scroll_panel.is_click_valid() + + rl.begin_scissor_mode(int(view_rect.x), int(view_rect.y), int(view_rect.width), int(view_rect.height)) + + for i, option in enumerate(self._options): + y_pos = view_rect.y + i * (self._option_height + 10) + offset.y + item_rect = rl.Rectangle(view_rect.x, y_pos, view_rect.width, self._option_height) + + if not rl.check_collision_recs(item_rect, view_rect): + continue + + is_selected = option == self._selection + button_style = ButtonStyle.PRIMARY if is_selected else ButtonStyle.NORMAL + + if gui_button(item_rect, option, button_style=button_style, text_alignment=TextAlignment.LEFT) and is_click_valid: + self._selection = option + + rl.end_scissor_mode() + + button_y = rect.y + rect.height - 80 - self._padding + button_width = (rect.width - 3 * self._padding) / 2 + + cancel_rect = rl.Rectangle(rect.x + self._padding, button_y, button_width, 80) + if gui_button(cancel_rect, "Cancel"): + return 0 # Canceled + + select_rect = rl.Rectangle(rect.x + 2 * self._padding + button_width, button_y, button_width, 80) + has_new_selection = self._selection != "" and self._selection != self._current + + if gui_button(select_rect, "Select", is_enabled=has_new_selection, button_style=ButtonStyle.PRIMARY): + return 1 # Selected + + return -1 # Still active + + +if __name__ == "__main__": + from openpilot.system.ui.lib.application import gui_app + + gui_app.init_window("Multi Option Dialog Example") + options = [f"Option {i}" for i in range(1, 11)] + dialog = MultiOptionDialog("Choose an option", options, options[0]) + + for _ in gui_app.render(): + result = dialog.render(rl.Rectangle(100, 100, 1024, 800)) + if result >= 0: + print(f"Selected: {dialog.selection}" if result > 0 else "Canceled") + break From 01867f1b2b59e817f185b6f3f7427080b1a43de1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 11 May 2025 21:40:06 +0800 Subject: [PATCH 011/142] system/ui: integrate WifiManagerUI into Updater (#35177) integrate WifiManagerUI into Updater --- system/ui/updater.py | 51 +++++++++++--------------------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/system/ui/updater.py b/system/ui/updater.py index eb9d766a16..42acaf5675 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -9,6 +9,9 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.label import gui_text_box, gui_label +from openpilot.system.ui.lib.wifi_manager import WifiManagerWrapper +from openpilot.system.ui.widgets.network import WifiManagerUI + # Constants MARGIN = 50 @@ -39,6 +42,8 @@ class Updater: self.show_reboot_button = False self.process = None self.update_thread = None + self.wifi_manager = WifiManagerWrapper() + self.wifi_manager_ui = WifiManagerUI(self.wifi_manager) def install_update(self): self.current_screen = Screen.PROGRESS @@ -79,8 +84,9 @@ class Updater: gui_label(title_rect, "Update Required", TITLE_FONT_SIZE, font_weight=FontWeight.BOLD) # Description - desc_text = "An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. \ - The download size is approximately 1GB." + desc_text = ("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. " + + "The download size is approximately 1GB.") + desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, gui_app.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3) gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE) @@ -101,49 +107,16 @@ class Updater: return # Return to avoid further processing after action def render_wifi_screen(self): - # Title and back button - title_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, 60) - gui_label(title_rect, "Wi-Fi Networks", 60, font_weight=FontWeight.BOLD) + # Draw the Wi-Fi manager UI + wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, gui_app.height - MARGIN * 2 - BUTTON_HEIGHT - 20) + self.wifi_manager_ui.render(wifi_rect) + back_button_rect = rl.Rectangle(MARGIN, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) if gui_button(back_button_rect, "Back"): self.current_screen = Screen.PROMPT return # Return to avoid processing other interactions after screen change - # Draw placeholder for WiFi implementation - placeholder_rect = rl.Rectangle( - MARGIN, - title_rect.y + title_rect.height + MARGIN, - gui_app.width - MARGIN * 2, - gui_app.height - title_rect.height - MARGIN * 3 - BUTTON_HEIGHT - ) - - # Draw rounded rectangle background - rl.draw_rectangle_rounded( - placeholder_rect, - 0.1, - 10, - rl.Color(41, 41, 41, 255) - ) - - # Draw placeholder text - placeholder_text = "WiFi Implementation Placeholder" - text_size = rl.measure_text_ex(gui_app.font(), placeholder_text, 80, 1) - text_pos = rl.Vector2( - placeholder_rect.x + (placeholder_rect.width - text_size.x) / 2, - placeholder_rect.y + (placeholder_rect.height - text_size.y) / 2 - ) - rl.draw_text_ex(gui_app.font(), placeholder_text, text_pos, 80, 1, rl.WHITE) - - # Draw instructions - instructions_text = "Real WiFi functionality would be implemented here" - instructions_size = rl.measure_text_ex(gui_app.font(), instructions_text, 40, 1) - instructions_pos = rl.Vector2( - placeholder_rect.x + (placeholder_rect.width - instructions_size.x) / 2, - text_pos.y + text_size.y + 20 - ) - rl.draw_text_ex(gui_app.font(), instructions_text, instructions_pos, 40, 1, rl.GRAY) - def render_progress_screen(self): title_rect = rl.Rectangle(MARGIN + 100, 330, gui_app.width - MARGIN * 2 - 200, 100) gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD) From e9cea3ae5cdd9b6cdfa1ca4e745224f5a7a560a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 11 May 2025 08:24:46 -0700 Subject: [PATCH 012/142] Revert TR again (#35179) Revert "Tomb Raider 7 (#35114)" This reverts commit 0fb4aafa35896e831bd58a636a9a1bb4a1442743. --- selfdrive/controls/lib/longitudinal_planner.py | 15 +++------------ selfdrive/modeld/fill_model_msg.py | 10 +++++----- selfdrive/modeld/modeld.py | 14 +++++--------- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- selfdrive/modeld/parse_model_outputs.py | 13 +++++++------ selfdrive/test/process_replay/ref_commit | 2 +- 7 files changed, 25 insertions(+), 37 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 4219e80f33..9d71faeca2 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -90,7 +90,7 @@ class LongitudinalPlanner: return x, v, a, j, throttle_prob def update(self, sm): - self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' + self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' if len(sm['carControl'].orientationNED) == 3: accel_coast = get_coast_accel(sm['carControl'].orientationNED[1]) @@ -113,7 +113,7 @@ class LongitudinalPlanner: # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - if self.mode == 'acc': + if self.mpc.mode == 'acc': accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) @@ -160,17 +160,8 @@ class LongitudinalPlanner: self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 action_t = self.CP.longitudinalActuatorDelay + DT_MDL - output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, + output_a_target, self.output_should_stop = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX, action_t=action_t, vEgoStopping=self.CP.vEgoStopping) - output_a_target_e2e = sm['modelV2'].action.desiredAcceleration - output_should_stop_e2e = sm['modelV2'].action.shouldStop - - if self.mode == 'acc': - output_a_target = output_a_target_mpc - self.output_should_stop = output_should_stop_mpc - else: - output_a_target = min(output_a_target_mpc, output_a_target_e2e) - self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc for idx in range(2): accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05) diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 36bd724d01..a91c6395c7 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -90,11 +90,11 @@ def fill_model_msg(base_msg: capnp._DynamicStructBuilder, extended_msg: capnp._D fill_xyzt(modelV2.orientationRate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) # temporal pose - #temporal_pose = modelV2.temporalPose - #temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist() - #temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist() - #temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist() - #temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist() + temporal_pose = modelV2.temporalPose + temporal_pose.trans = net_output_data['sim_pose'][0,:ModelConstants.POSE_WIDTH//2].tolist() + temporal_pose.transStd = net_output_data['sim_pose_stds'][0,:ModelConstants.POSE_WIDTH//2].tolist() + temporal_pose.rot = net_output_data['sim_pose'][0,ModelConstants.POSE_WIDTH//2:].tolist() + temporal_pose.rotStd = net_output_data['sim_pose_stds'][0,ModelConstants.POSE_WIDTH//2:].tolist() # poly path fill_xyz_poly(driving_model_data.path, ModelConstants.POLY_PATH_DEGREE, *net_output_data['plan'][0,:,Plan.POSITION].T) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 84d7760dce..5e1acdea0e 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -26,7 +26,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS from openpilot.common.transformations.model import get_warp_matrix from openpilot.system import sentry from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper -from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan +from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -41,8 +41,8 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' -LAT_SMOOTH_SECONDS = 0.1 -LONG_SMOOTH_SECONDS = 0.3 +LAT_SMOOTH_SECONDS = 0.0 +LONG_SMOOTH_SECONDS = 0.0 MIN_LAT_CONTROL_SPEED = 0.3 @@ -55,11 +55,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log. action_t=long_action_t) desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS) - desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2], - plan[:,Plan.ORIENTATION_RATE][:,2], - ModelConstants.T_IDXS, - v_ego, - lat_action_t) + desired_curvature = model_output['desired_curvature'][0, 0] if v_ego > MIN_LAT_CONTROL_SPEED: desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS) else: @@ -176,7 +172,7 @@ class ModelState: # TODO model only uses last value now self.full_prev_desired_curv[0,:-1] = self.full_prev_desired_curv[0,1:] self.full_prev_desired_curv[0,-1,:] = policy_outputs_dict['desired_curvature'][0, :] - self.numpy_inputs['prev_desired_curv'][:] = 0*self.full_prev_desired_curv[0, self.temporal_idxs] + self.numpy_inputs['prev_desired_curv'][:] = self.full_prev_desired_curv[0, self.temporal_idxs] combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 5bd77bb958..3601bbb5da 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19e30484236efff72d519938c3e26461dbeb89c11d81fa7ecbff8e0263333c18 -size 15588463 +oid sha256:98f0121ccb6f850077b04cc91bd33d370fc6cbdc2bd35f1ab55628a15a813f36 +size 15966721 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 0a7b4a803d..aee6d8f1bf 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dad289ae367cefcb862ef1d707fb4919d008f0eeaa1ebaf18df58d8de5a7d96e -size 46265585 +oid sha256:897f80d0388250f99bba69b6a8434560cc0fd83157cbeb0bc134c67fe4e64624 +size 34882971 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 783572d436..810c44ccb9 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -88,12 +88,6 @@ class Parser: self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) - self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, - out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) - for k in ['lead_prob', 'lane_lines_prob']: - self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) self.parse_binary_crossentropy('meta', outs) return outs @@ -101,10 +95,17 @@ class Parser: def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) + self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) + self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, + out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) if 'lat_planner_solution' in outs: self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) if 'desired_curvature' in outs: self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) + for k in ['lead_prob', 'lane_lines_prob']: + self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) return outs diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 3dd3eab25f..1ae4c4a1a8 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -7bf4ae5b92a3ad1f073f675e24e28babad0f2aa0 \ No newline at end of file +7bf4ae5b92a3ad1f073f675e24e28babad0f2aa0 From bfadab078e01bb0058de3c58069e4ea5d493198d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 12 May 2025 02:05:59 +0800 Subject: [PATCH 013/142] system/ui: set log level to WARNING to reduce noise (#35180) * set log level to WARNING to reduce noise * custom callback handling for raylib logs --- system/ui/lib/application.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index eae5962277..3f3595e8f5 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -40,6 +40,7 @@ class GuiApplication: self._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False + self._trace_log_callback = None def request_close(self): self._window_close_requested = True @@ -50,6 +51,9 @@ class GuiApplication: HARDWARE.set_display_power(True) HARDWARE.set_screen_brightness(65) + self._set_log_callback() + + rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT) rl.init_window(self._width, self._height, title) rl.set_target_fps(fps) @@ -137,6 +141,29 @@ class GuiApplication: rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR)) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255))) + def _set_log_callback(self): + @rl.ffi.callback("void(int, char *, void *)") + def trace_log_callback(log_level, text, args): + try: + text_str = rl.ffi.string(text).decode('utf-8') + except (TypeError, UnicodeDecodeError): + text_str = str(text) + + if log_level == rl.TraceLogLevel.LOG_ERROR: + cloudlog.error(f"raylib: {text_str}") + elif log_level == rl.TraceLogLevel.LOG_WARNING: + cloudlog.warning(f"raylib: {text_str}") + elif log_level == rl.TraceLogLevel.LOG_INFO: + cloudlog.info(f"raylib: {text_str}") + elif log_level == rl.TraceLogLevel.LOG_DEBUG: + cloudlog.debug(f"raylib: {text_str}") + else: + cloudlog.debug(f"raylib: Unknown level {log_level}: {text_str}") + + # Store callback reference + self._trace_log_callback = trace_log_callback + rl.set_trace_log_callback(self._trace_log_callback) + def _monitor_fps(self): fps = rl.get_fps() From e62b6a38e9cf6431321dafefc78ea86c9a1ccf13 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 12 May 2025 02:06:04 +0800 Subject: [PATCH 014/142] system/ui: set keyboard to use full window area (#35181) set keyboard to use full window area --- system/ui/widgets/keyboard.py | 5 ++++- system/ui/widgets/network.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 13fdb912b5..d496c5f749 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,8 +1,10 @@ import pyray as rl +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.button import gui_button from openpilot.system.ui.lib.label import gui_label # Constants for special keys +CONTENT_MARGIN = 50 BACKSPACE_KEY = "<-" ENTER_KEY = "Enter" SPACE_KEY = " " @@ -55,7 +57,8 @@ class Keyboard: self._clear() return result - def render(self, rect, title, sub_title): + def render(self, title, sub_title): + rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN) gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"): diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 14f4017925..49e2dd8296 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -62,7 +62,7 @@ class WifiManagerUI: match self.state: case StateNeedsAuth(network): - result = self.keyboard.render(rect, "Enter password", f"for {network.ssid}") + result = self.keyboard.render("Enter password", f"for {network.ssid}") if result == 1: self.connect_to_network(network, self.keyboard.text) elif result == 0: From 35dbdf912478da5db58aa88a1b8e64e59f27ad3c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 11 May 2025 11:08:50 -0700 Subject: [PATCH 015/142] raylib: fwd everything to cloudlog --- system/ui/lib/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 3f3595e8f5..c359b1e578 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -53,7 +53,7 @@ class GuiApplication: self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) + rl.set_trace_log_level(rl.TraceLogLevel.LOG_ALL) rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT) rl.init_window(self._width, self._height, title) rl.set_target_fps(fps) @@ -158,7 +158,7 @@ class GuiApplication: elif log_level == rl.TraceLogLevel.LOG_DEBUG: cloudlog.debug(f"raylib: {text_str}") else: - cloudlog.debug(f"raylib: Unknown level {log_level}: {text_str}") + cloudlog.error(f"raylib: Unknown level {log_level}: {text_str}") # Store callback reference self._trace_log_callback = trace_log_callback From 2b32325be1c32445f3e069f1536f14d4c6affc6b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 12 May 2025 22:21:12 +0800 Subject: [PATCH 016/142] system/ui: Full-Featured InputBox Component (#35178) custom input box component --- system/ui/lib/inputbox.py | 157 ++++++++++++++++++++++++++++++++++ system/ui/widgets/keyboard.py | 37 +++----- system/ui/widgets/network.py | 4 +- 3 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 system/ui/lib/inputbox.py diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py new file mode 100644 index 0000000000..9bac5251aa --- /dev/null +++ b/system/ui/lib/inputbox.py @@ -0,0 +1,157 @@ +import pyray as rl +from openpilot.system.ui.lib.application import gui_app + + +class InputBox: + def __init__(self, max_text_size=255, password_mode=False): + self._max_text_size = max_text_size + self._input_text = "" + self._cursor_position = 0 + self._password_mode = password_mode + self._blink_counter = 0 + self._show_cursor = False + self._last_key_pressed = 0 + self._key_press_time = 0 + self._repeat_delay = 30 + self._repeat_rate = 5 + + @property + def text(self): + return self._input_text + + @text.setter + def text(self, value): + self._input_text = value[: self._max_text_size] + self._cursor_position = len(self._input_text) + + def set_password_mode(self, password_mode): + self._password_mode = password_mode + + def clear(self): + self._input_text = '' + self._cursor_position = 0 + + def set_cursor_position(self, position): + """Set the cursor position and reset the blink counter.""" + if 0 <= position <= len(self._input_text): + self._cursor_position = position + self._blink_counter = 0 + self._show_cursor = True + + def add_char_at_cursor(self, char): + """Add a character at the current cursor position.""" + if len(self._input_text) < self._max_text_size: + self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position :] + self.set_cursor_position(self._cursor_position + 1) + return True + return False + + def delete_char_before_cursor(self): + """Delete the character before the cursor position (backspace).""" + if self._cursor_position > 0: + self._input_text = self._input_text[: self._cursor_position - 1] + self._input_text[self._cursor_position :] + self.set_cursor_position(self._cursor_position - 1) + return True + return False + + def delete_char_at_cursor(self): + """Delete the character at the cursor position (delete).""" + if self._cursor_position < len(self._input_text): + self._input_text = self._input_text[: self._cursor_position] + self._input_text[self._cursor_position + 1 :] + self.set_cursor_position(self._cursor_position) + return True + return False + + def render(self, rect, color=rl.LIGHTGRAY, border_color=rl.DARKGRAY, text_color=rl.BLACK, font_size=80): + # Handle mouse input + self._handle_mouse_input(rect, font_size) + + # Draw input box + rl.draw_rectangle_rec(rect, color) + rl.draw_rectangle_lines_ex(rect, 1, border_color) + + # Process keyboard input + self._handle_keyboard_input() + + # Update cursor blink + self._blink_counter += 1 + if self._blink_counter >= 30: + self._show_cursor = not self._show_cursor + self._blink_counter = 0 + + # Display text + font = gui_app.font() + display_text = "•" * len(self._input_text) if self._password_mode else self._input_text + padding = 10 + rl.draw_text_ex( + font, + display_text, + rl.Vector2(int(rect.x + padding), int(rect.y + rect.height / 2 - font_size / 2)), + font_size, + 0, + text_color, + ) + + # Draw cursor + if self._show_cursor: + cursor_x = rect.x + padding + if len(display_text) > 0 and self._cursor_position > 0: + cursor_x += rl.measure_text_ex(font, display_text[: self._cursor_position], font_size, 0).x + + cursor_height = font_size + 4 + cursor_y = rect.y + rect.height / 2 - cursor_height / 2 + rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.BLACK) + + def _handle_mouse_input(self, rect, font_size): + """Handle mouse clicks to position cursor.""" + mouse_pos = rl.get_mouse_position() + if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect): + # Calculate cursor position from click + if len(self._input_text) > 0: + text_width = rl.measure_text(self._input_text, font_size) + text_pos_x = rect.x + 10 + + if mouse_pos.x - text_pos_x > text_width: + self.set_cursor_position(len(self._input_text)) + else: + click_ratio = (mouse_pos.x - text_pos_x) / text_width + self.set_cursor_position(int(len(self._input_text) * click_ratio)) + + self.set_cursor_position(0) + + def _handle_keyboard_input(self): + """Process keyboard input.""" + key = rl.get_key_pressed() + + # Handle key repeats + if key == self._last_key_pressed and key != 0: + self._key_press_time += 1 + if self._key_press_time > self._repeat_delay and self._key_press_time % self._repeat_rate == 0: + # Process repeated key + pass + else: + return # Skip processing until repeat triggers + else: + self._last_key_pressed = key + self._key_press_time = 0 + + # Handle navigation keys + if key == rl.KEY_LEFT: + if self._cursor_position > 0: + self.set_cursor_position(self._cursor_position - 1) + elif key == rl.KEY_RIGHT: + if self._cursor_position < len(self._input_text): + self.set_cursor_position(self._cursor_position + 1) + elif key == rl.KEY_BACKSPACE: + self.delete_char_before_cursor() + elif key == rl.KEY_DELETE: + self.delete_char_at_cursor() + elif key == rl.KEY_HOME: + self.set_cursor_position(0) + elif key == rl.KEY_END: + self.set_cursor_position(len(self._input_text)) + + # Handle text input + char = rl.get_char_pressed() + if char != 0 and char >= 32: # Filter out control characters + self.add_char_at_cursor(chr(char)) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index d496c5f749..d81bf3090e 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,6 +1,7 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.button import gui_button +from openpilot.system.ui.lib.inputbox import InputBox from openpilot.system.ui.lib.label import gui_label # Constants for special keys @@ -47,28 +48,25 @@ class Keyboard: def __init__(self, max_text_size: int = 255): self._layout = keyboard_layouts["lowercase"] self._max_text_size = max_text_size - self._string_pointer = rl.ffi.new("char[]", max_text_size) - self._input_text = "" - self._clear() + self._input_box = InputBox(max_text_size) @property def text(self): - result = rl.ffi.string(self._string_pointer).decode("utf-8") - self._clear() - return result + return self._input_box.text + + def clear(self): + self._input_box.clear() def render(self, title, sub_title): rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN) gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"): - self._clear() + self.clear() return 0 # Text box for input - self._sync_string_pointer() - rl.gui_text_box(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._string_pointer, self._max_text_size, True) - self._input_text = rl.ffi.string(self._string_pointer).decode("utf-8") + self._input_box.render(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100)) h_space, v_space = 15, 15 row_y_start = rect.y + 300 # Starting Y position for the first row key_height = (rect.height - 300 - 3 * v_space) / 4 @@ -104,18 +102,7 @@ class Keyboard: self._layout = keyboard_layouts["numbers"] elif key == SYMBOL_KEY: self._layout = keyboard_layouts["specials"] - elif key == BACKSPACE_KEY and len(self._input_text) > 0: - self._input_text = self._input_text[:-1] - elif key != BACKSPACE_KEY and len(self._input_text) < self._max_text_size: - self._input_text += key - - def _clear(self): - self._input_text = '' - self._string_pointer[0] = b'\0' - - def _sync_string_pointer(self): - """Sync the C-string pointer with the internal Python string.""" - encoded = self._input_text.encode("utf-8")[:self._max_text_size - 1] # Leave room for the null terminator - buffer = rl.ffi.buffer(self._string_pointer) - buffer[:len(encoded)] = encoded - self._string_pointer[len(encoded)] = b'\0' # Null terminator + elif key == BACKSPACE_KEY: + self._input_box.delete_char_before_cursor() + else: + self._input_box.add_char_at_cursor(key) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 49e2dd8296..a265111a46 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -64,7 +64,9 @@ class WifiManagerUI: case StateNeedsAuth(network): result = self.keyboard.render("Enter password", f"for {network.ssid}") if result == 1: - self.connect_to_network(network, self.keyboard.text) + password = self.keyboard.text + self.keyboard.clear() + self.connect_to_network(network, password) elif result == 0: self.state = StateIdle() From 59fbeb60707e3a3cb16064dda308260193e00a95 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 12 May 2025 15:28:36 +0100 Subject: [PATCH 017/142] system/ui: keyboard demo script --- system/ui/widgets/keyboard.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index d81bf3090e..0723e65a16 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -57,7 +57,7 @@ class Keyboard: def clear(self): self._input_box.clear() - def render(self, title, sub_title): + def render(self, title: str, sub_title: str): rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN) gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) @@ -106,3 +106,17 @@ class Keyboard: self._input_box.delete_char_before_cursor() else: self._input_box.add_char_at_cursor(key) + + +if __name__ == "__main__": + gui_app.init_window("Keyboard") + keyboard = Keyboard() + for _ in gui_app.render(): + result = keyboard.render("Keyboard", "Type here") + if result == 1: + print(f"You typed: {keyboard.text}") + gui_app.request_close() + elif result == 0: + print("Canceled") + gui_app.request_close() + gui_app.close() From 43054a56c263ad84978e9cfb6cc05d92e0a212ee Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 12 May 2025 15:39:48 +0100 Subject: [PATCH 018/142] system/ui: fix tethering ssid (#35188) system/ui: fix tethering ssid when no params --- system/ui/lib/wifi_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 00649b146b..e08e4cbb62 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -79,7 +79,11 @@ class WifiManager: self.saved_connections: dict[str, str] = {} self.active_ap_path: str = "" self.scan_task: asyncio.Task | None = None - self._tethering_ssid = "weedle-" + Params().get("DongleId", encoding="utf-8") + # Set tethering ssid as "weedle" + first 4 characters of a dongle id + self._tethering_ssid = "weedle" + dongle_id = Params().get("DongleId", encoding="utf-8") + if dongle_id: + self._tethering_ssid += "-" + dongle_id[:4] self.running: bool = True self._current_connection_ssid: str | None = None From 02125f68629153f2cd7827fab64c011821d0ea10 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 12 May 2025 23:59:00 +0800 Subject: [PATCH 019/142] system/ui: fix cursor position in inputBox mouse click handler (#35189) fix cursor position in inputBox mouse click handler --- system/ui/lib/inputbox.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index 9bac5251aa..d933d42476 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -108,7 +108,7 @@ class InputBox: if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect): # Calculate cursor position from click if len(self._input_text) > 0: - text_width = rl.measure_text(self._input_text, font_size) + text_width = rl.measure_text_ex(gui_app.font(), self._input_text, font_size, 0).x text_pos_x = rect.x + 10 if mouse_pos.x - text_pos_x > text_width: @@ -116,7 +116,7 @@ class InputBox: else: click_ratio = (mouse_pos.x - text_pos_x) / text_width self.set_cursor_position(int(len(self._input_text) * click_ratio)) - + else: self.set_cursor_position(0) def _handle_keyboard_input(self): From 98dba943b2519c322e28a0d636cbbbf67c930d51 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 12 May 2025 09:30:01 -0700 Subject: [PATCH 020/142] feat(clip): add timestamp onto clip (#35182) * feat(clip): add timestamp onto clip * same font size as title * revert * just declare it here, and add comments --- tools/clip/run.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 33c98a0213..12ac879baa 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -165,12 +165,16 @@ def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, rou # TODO: evaluate creating fn that inspects /tmp/.X11-unix and creates unused display to avoid possibility of collision display = f':{randint(99, 999)}' + box_style = 'box=1:boxcolor=black@0.33:boxborderw=7' meta_text = get_meta_text(route) overlays = [ - f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile=Inter.tff:fontcolor=white:fontsize=18:box=1:boxcolor=black@0.33:boxborderw=7:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'" + # metadata overlay + f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile=Inter.tff:fontcolor=white:fontsize=18:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", + # route time overlay + f"drawtext=text='%{{eif\\:floor(({start}+t)/60)\\:d\\:2}}\\:%{{eif\\:mod({start}+t\\,60)\\:d\\:2}}':fontfile=Inter.tff:fontcolor=white:fontsize=24:{box_style}:x=w-text_w-38:y=38" ] if title: - overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile=Inter.tff:fontcolor=white:fontsize=32:box=1:boxcolor=black@0.33:boxborderw=10:x=(w-text_w)/2:y=53") + overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile=Inter.tff:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") ffmpeg_cmd = [ 'ffmpeg', '-y', '-video_size', RESOLUTION, '-framerate', str(FRAMERATE), '-f', 'x11grab', '-draw_mouse', '0', From 8c995ab26dbdba821b5fad17cd4d29dec676d314 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 May 2025 00:32:20 +0800 Subject: [PATCH 021/142] system/ui: implement CameraView widget (#35185) * initial commit * run at 60hz, nonblock * graceful cleanup * re-position * rename --------- Co-authored-by: Cameron Clough --- system/ui/widgets/cameraview.py | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 system/ui/widgets/cameraview.py diff --git a/system/ui/widgets/cameraview.py b/system/ui/widgets/cameraview.py new file mode 100644 index 0000000000..841440ae3a --- /dev/null +++ b/system/ui/widgets/cameraview.py @@ -0,0 +1,87 @@ +import pyray as rl +from msgq.visionipc import VisionIpcClient, VisionStreamType +from openpilot.system.ui.lib.application import gui_app + +FRAME_FRAGMENT_SHADER = """ +#version 330 core +in vec2 fragTexCoord; uniform sampler2D texture0, texture1; out vec4 fragColor; +void main() { + float y = texture(texture0, fragTexCoord).r; + vec2 uv = texture(texture1, fragTexCoord).ra - 0.5; + fragColor = vec4(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x, 1.0); +}""" + + +class CameraView: + def __init__(self, name: str, stream_type: VisionStreamType): + self.client = VisionIpcClient(name, stream_type, False) + self.shader = rl.load_shader_from_memory(rl.ffi.NULL, FRAME_FRAGMENT_SHADER) + self.texture_y: rl.Texture | None = None + self.texture_uv: rl.Texture | None = None + self.frame = None + + def close(self): + self._clear_textures() + if self.shader and self.shader.id: + rl.unload_shader(self.shader) + + def render(self, rect: rl.Rectangle): + if not self._ensure_connection(): + return + + buffer = self.client.recv(timeout_ms=0) + self.frame = buffer if buffer else self.frame + if not self.frame or not self.texture_y or not self.texture_uv: + return + + y_data = self.frame.data[: self.frame.uv_offset] + uv_data = self.frame.data[self.frame.uv_offset :] + + rl.update_texture(self.texture_y, rl.ffi.cast("void *", y_data.ctypes.data)) + rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data)) + + # Calculate scaling to maintain aspect ratio + scale = min(rect.width / self.frame.width, rect.height / self.frame.height) + x_offset = rect.x + (rect.width - (self.frame.width * scale)) / 2 + y_offset = rect.y + (rect.height - (self.frame.height * scale)) / 2 + src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height)) + dst_rect = rl.Rectangle(x_offset, y_offset, self.frame.width * scale, self.frame.height * scale) + + rl.begin_shader_mode(self.shader) + rl.set_shader_value_texture(self.shader, rl.get_shader_location(self.shader, "texture1"), self.texture_uv) + rl.draw_texture_pro(self.texture_y, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) + rl.end_shader_mode() + + def _ensure_connection(self) -> bool: + if not self.client.is_connected(): + self.frame = None + if not self.client.connect(False) or not self.client.num_buffers: + return False + + self._clear_textures() + self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), + int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) + self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), + int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA)) + return True + + def _clear_textures(self): + if self.texture_y and self.texture_y.id: + rl.unload_texture(self.texture_y) + if self.texture_uv and self.texture_uv.id: + rl.unload_texture(self.texture_uv) + +if __name__ == "__main__": + gui_app.init_window("watch3") + road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + driver_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) + wide_road_camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD) + try: + for _ in gui_app.render(): + road_camera_view.render(rl.Rectangle(gui_app.width // 4, 0, gui_app.width // 2, gui_app.height // 2)) + driver_camera_view.render(rl.Rectangle(0, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2)) + wide_road_camera_view.render(rl.Rectangle(gui_app.width // 2, gui_app.height // 2, gui_app.width // 2, gui_app.height // 2)) + finally: + road_camera_view.close() + driver_camera_view.close() + wide_road_camera_view.close() From ce4fda1f92cd4d1cccfe4e9ec123ce3a5068d96e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 May 2025 02:01:25 +0800 Subject: [PATCH 022/142] system/ui: add minimum WIFI password length validation (#35190) * add minimum WIFI password length validation * add min text size to keyboard * disable enter if text size= self._min_text_size + if gui_button(key_rect, key, is_enabled=is_enabled): if key == ENTER_KEY: return 1 else: @@ -110,7 +112,7 @@ class Keyboard: if __name__ == "__main__": gui_app.init_window("Keyboard") - keyboard = Keyboard() + keyboard = Keyboard(min_text_size=8) for _ in gui_app.render(): result = keyboard.render("Keyboard", "Type here") if result == 1: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index a265111a46..06448483aa 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -11,6 +11,8 @@ from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog NM_DEVICE_STATE_NEED_AUTH = 60 +MIN_PASSWORD_LENGTH = 8 +MAX_PASSWORD_LENGTH = 64 ITEM_HEIGHT = 160 @@ -46,7 +48,7 @@ class WifiManagerUI: self.state: UIState = StateIdle() self.btn_width = 200 self.scroll_panel = GuiScrollPanel() - self.keyboard = Keyboard() + self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH) self._networks: list[NetworkInfo] = [] @@ -66,7 +68,9 @@ class WifiManagerUI: if result == 1: password = self.keyboard.text self.keyboard.clear() - self.connect_to_network(network, password) + + if len(password) >= MIN_PASSWORD_LENGTH: + self.connect_to_network(network, password) elif result == 0: self.state = StateIdle() From b2d414cc8ba3db7d534d676084704a40e735aa26 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 12 May 2025 13:01:29 -0700 Subject: [PATCH 023/142] [bot] Update Python packages (#35187) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- uv.lock | 1304 +++++++++++++++++++++++++------------------------- 2 files changed, 655 insertions(+), 651 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index c856a2c0bd..b922517802 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c856a2c0bd2b3c75f86a73b051c0c4cc7159559e +Subproject commit b9225178021be2b606dab89bf941440562f4cc6c diff --git a/uv.lock b/uv.lock index fbeda4d552..60e0e521c5 100644 --- a/uv.lock +++ b/uv.lock @@ -163,7 +163,7 @@ wheels = [ [[package]] name = "azure-identity" -version = "1.21.0" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -172,9 +172,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/a1/f1a683672e7a88ea0e3119f57b6c7843ed52650fdcac8bfa66ed84e86e40/azure_identity-1.21.0.tar.gz", hash = "sha256:ea22ce6e6b0f429bc1b8d9212d5b9f9877bd4c82f1724bfa910760612c07a9a6", size = 266445, upload-time = "2025-03-11T20:53:07.463Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/8e/1b5916f5e1696bf05b009cf7d41383cea54aa8536d4a4f6f88cca15eb6a4/azure_identity-1.22.0.tar.gz", hash = "sha256:c8f5ef23e5295c2fa300c984dd9f5e1fe43503fc25c121c37ff6a15e39b800b9", size = 263346, upload-time = "2025-05-06T20:22:24.13Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9f/1f9f3ef4f49729ee207a712a5971a9ca747f2ca47d9cbf13cf6953e3478a/azure_identity-1.21.0-py3-none-any.whl", hash = "sha256:258ea6325537352440f71b35c3dffe9d240eae4a5126c1b7ce5efd5766bd9fd9", size = 189190, upload-time = "2025-03-11T20:53:09.197Z" }, + { url = "https://files.pythonhosted.org/packages/06/1a/6f13d7f95f68f37303c0e00e011d498e4524e70d354b2e11ef5ae89e0ce0/azure_identity-1.22.0-py3-none-any.whl", hash = "sha256:26d6c63f2ca453c77c3e74be8613941ad074e05d0c8be135247573752c249ad8", size = 185524, upload-time = "2025-05-06T20:22:25.991Z" }, ] [[package]] @@ -295,14 +295,14 @@ wheels = [ [[package]] name = "click" -version = "8.1.8" +version = "8.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, ] [[package]] @@ -447,27 +447,31 @@ wheels = [ [[package]] name = "cython" -version = "3.0.12" +version = "3.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/25/886e197c97a4b8e254173002cdc141441e878ff29aaa7d9ba560cd6e4866/cython-3.0.12.tar.gz", hash = "sha256:b988bb297ce76c671e28c97d017b95411010f7c77fa6623dd0bb47eed1aee1bc", size = 2757617, upload-time = "2025-02-11T09:05:50.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/f7/db37a613aec5abcd51c8000a386a701ac32e94659aa03fa69c3e5c19b149/cython-3.1.0.tar.gz", hash = "sha256:1097dd60d43ad0fff614a57524bfd531b35c13a907d13bee2cc2ec152e6bf4a1", size = 3181017, upload-time = "2025-05-08T20:25:36.12Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/60/3d27abd940f7b80a6aeb69dc093a892f04828e1dd0b243dd81ff87d7b0e9/Cython-3.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feb86122a823937cc06e4c029d80ff69f082ebb0b959ab52a5af6cdd271c5dc3", size = 3277430, upload-time = "2025-02-11T09:06:47.253Z" }, - { url = "https://files.pythonhosted.org/packages/c7/49/f17b0541b317d11f1d021a580643ee2481685157cded92efb32e2fb4daef/Cython-3.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfdbea486e702c328338314adb8e80f5f9741f06a0ae83aaec7463bc166d12e8", size = 3444055, upload-time = "2025-02-11T09:06:50.807Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7f/c57791ba6a1c934b6f1ab51371e894e3b4bfde0bc35e50046c8754a9d215/Cython-3.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563de1728c8e48869d2380a1b76bbc1b1b1d01aba948480d68c1d05e52d20c92", size = 3597874, upload-time = "2025-02-11T09:06:54.806Z" }, - { url = "https://files.pythonhosted.org/packages/23/24/803a0db3681b3a2ef65a4bebab201e5ae4aef5e6127ae03683476a573aa9/Cython-3.0.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398d4576c1e1f6316282aa0b4a55139254fbed965cba7813e6d9900d3092b128", size = 3644129, upload-time = "2025-02-11T09:06:58.152Z" }, - { url = "https://files.pythonhosted.org/packages/27/13/9b53ba8336e083ece441af8d6d182b8ca83ad523e87c07b3190af379ebc3/Cython-3.0.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e5eadef80143026944ea8f9904715a008f5108d1d644a89f63094cc37351e73", size = 3504936, upload-time = "2025-02-11T09:07:01.592Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d2/d11104be6992a9fe256860cae6d1a79f7dcf3bdb12ae00116fac591f677d/Cython-3.0.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a93cbda00a5451175b97dea5a9440a3fcee9e54b4cba7a7dbcba9a764b22aec", size = 3713066, upload-time = "2025-02-11T09:07:03.961Z" }, - { url = "https://files.pythonhosted.org/packages/d9/8c/1fe49135296efa3f460c760a4297f6a5b387f3e69ac5c9dcdbd620295ab3/Cython-3.0.12-cp311-cp311-win32.whl", hash = "sha256:3109e1d44425a2639e9a677b66cd7711721a5b606b65867cb2d8ef7a97e2237b", size = 2579935, upload-time = "2025-02-11T09:07:06.947Z" }, - { url = "https://files.pythonhosted.org/packages/02/4e/5ac0b5b9a239cd3fdae187dda8ff06b0b812f671e2501bf253712278f0ac/Cython-3.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:d4b70fc339adba1e2111b074ee6119fe9fd6072c957d8597bce9a0dd1c3c6784", size = 2787337, upload-time = "2025-02-11T09:07:10.087Z" }, - { url = "https://files.pythonhosted.org/packages/e6/6c/3be501a6520a93449b1e7e6f63e598ec56f3b5d1bc7ad14167c72a22ddf7/Cython-3.0.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe030d4a00afb2844f5f70896b7f2a1a0d7da09bf3aa3d884cbe5f73fff5d310", size = 3311717, upload-time = "2025-02-11T09:07:12.405Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ab/adfeb22c85491de18ae10932165edd5b6f01e4c5e3e363638759d1235015/Cython-3.0.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7fec4f052b8fe173fe70eae75091389955b9a23d5cec3d576d21c5913b49d47", size = 3344337, upload-time = "2025-02-11T09:07:14.979Z" }, - { url = "https://files.pythonhosted.org/packages/0d/72/743730d7c46b4c85abefb93187cbbcb7aae8de288d7722b990db3d13499e/Cython-3.0.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0faa5e39e5c8cdf6f9c3b1c3f24972826e45911e7f5b99cf99453fca5432f45e", size = 3517692, upload-time = "2025-02-11T09:07:17.45Z" }, - { url = "https://files.pythonhosted.org/packages/09/a1/29a4759a02661f8c8e6b703f62bfbc8285337e6918cc90f55dc0fadb5eb3/Cython-3.0.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d53de996ed340e9ab0fc85a88aaa8932f2591a2746e1ab1c06e262bd4ec4be7", size = 3577057, upload-time = "2025-02-11T09:07:22.106Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f8/03d74e98901a7cc2f21f95231b07dd54ec2f69477319bac268b3816fc3a8/Cython-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea3a0e19ab77266c738aa110684a753a04da4e709472cadeff487133354d6ab8", size = 3396493, upload-time = "2025-02-11T09:07:25.183Z" }, - { url = "https://files.pythonhosted.org/packages/50/ea/ac33c5f54f980dbc23dd8f1d5c51afeef26e15ac1a66388e4b8195af83b7/Cython-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151082884be468f2f405645858a857298ac7f7592729e5b54788b5c572717ba", size = 3603859, upload-time = "2025-02-11T09:07:27.634Z" }, - { url = "https://files.pythonhosted.org/packages/a2/4e/91fc1d6b5e678dcf2d1ecd8dce45b014b4b60d2044d376355c605831c873/Cython-3.0.12-cp312-cp312-win32.whl", hash = "sha256:3083465749911ac3b2ce001b6bf17f404ac9dd35d8b08469d19dc7e717f5877a", size = 2610428, upload-time = "2025-02-11T09:07:30.719Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a7fdec227b9f0bb07edbeb016c7b18ed6a8e6ce884d08b2e397cda2c0168/Cython-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:c0b91c7ebace030dd558ea28730de8c580680b50768e5af66db2904a3716c3e3", size = 2794755, upload-time = "2025-02-11T09:07:36.021Z" }, - { url = "https://files.pythonhosted.org/packages/27/6b/7c87867d255cbce8167ed99fc65635e9395d2af0f0c915428f5b17ec412d/Cython-3.0.12-py2.py3-none-any.whl", hash = "sha256:0038c9bae46c459669390e53a1ec115f8096b2e4647ae007ff1bf4e6dee92806", size = 1171640, upload-time = "2025-02-11T09:05:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/eb20821704e0bac5051ae552db574cf5022ba658006a0c187d85d50cfd69/cython-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c736405078dc376502617eb41c39e223ae176ebd1a4ddc18179d2517bc8c8658", size = 3004811, upload-time = "2025-05-08T21:20:38.179Z" }, + { url = "https://files.pythonhosted.org/packages/59/6f/2f4e1382e365006055ec7b803372283065c4cef6f5bab88f8ed2ab350452/cython-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1215d3adb4e8691d03e712aed31206d21f387a8003d8de6a574ee75fe6d2e07c", size = 2868305, upload-time = "2025-05-08T21:20:41.1Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b3/9778e21b317c10e9a1028270eb87eda91202cd37ee1e0c09630a600629ef/cython-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:522d4dae1fea71eee5c944fb7a8530de8bdd6df0ccb2bd001d0f75be228eac6c", size = 3130151, upload-time = "2025-05-08T21:20:44.436Z" }, + { url = "https://files.pythonhosted.org/packages/58/e7/4fe8304d1caccff52a4c042274b42bf23574a380e64a86daaf4c695be510/cython-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462ad6142057e81715ada74e2d24b9a07bf36ae3da72bf973478b5c3e809c26d", size = 3220382, upload-time = "2025-05-08T21:20:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/0a/88/9d3d7a1cb8ce3e93acbabac6f2275cfaa1021c2ddf93d2c0999bb2ee4d43/cython-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8f00cdeb14f004ebeacf946e06bad2e3ed5776af96f5af95f92d822c4ba275f", size = 3287586, upload-time = "2025-05-08T21:20:49.72Z" }, + { url = "https://files.pythonhosted.org/packages/91/49/99148b55ad1c036fb1cb69bf3729d2beecc4246f7c5f487006f16ec1f394/cython-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37d62b8b8919126c75769e5470b288d76c83a1645e73c7aca4b7d7aecb3c1234", size = 3175407, upload-time = "2025-05-08T21:20:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/0e/52/297ce50c21647ae3741856e57f04ba69b1819e5658756823a3a1efd05e8e/cython-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bea0b6bfde7493acb0529fc603abd4b3b13c3bb2fff7a889ae5a8d3ea7dc5a84", size = 3381904, upload-time = "2025-05-08T21:20:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/61/ab/2966d2786f4e6a7903d23b019a810675334960e8d88686a985286bd8f53d/cython-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fe8c1db9ec03d9ef83e33c842c108e892577ade4c5f530c9435beced048e4698", size = 3305998, upload-time = "2025-05-08T21:20:56.588Z" }, + { url = "https://files.pythonhosted.org/packages/62/52/0670d10b563b2f345477a158bd133399ddb9e7f6ff16e397e77932c07026/cython-3.1.0-cp311-cp311-win32.whl", hash = "sha256:5f6417d378bd11ca55f16e3c1c7c3bf6d7f0a0cc326c46e309fcba46c54ba4f1", size = 2454005, upload-time = "2025-05-08T21:20:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/53/6e/3ea341450bc40f3c0c3da9ae05c22eeb59930531accba78a4b3eebaf342c/cython-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:dde3726aa5acbe879f849a09606b886491f950cfa993b435e50e9561fdf731c6", size = 2663258, upload-time = "2025-05-08T21:21:00.903Z" }, + { url = "https://files.pythonhosted.org/packages/e9/64/ae1d8848550ec3975634fcf189ccc85e73c3b9f76369dd85c484f2f8f1c3/cython-3.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8f8c4753f6b926046c0cdf6037ba8560f6677730bf0ab9c1db4e0163b4bb30f9", size = 3012737, upload-time = "2025-05-08T21:21:06.024Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e6/c8fe5e71b4272a1b5683a006bfe4bfeb5e2691c6603b44d18eb57772f626/cython-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db8e15c8eeee529468eab08528c9bf714a94354b34375be6c0c110f6012a4768", size = 2843774, upload-time = "2025-05-08T21:21:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/6a/eb/aeb62d32049ab55555e679b7d0cdb2f8c16f6a9d1189a56a96a0c1e86083/cython-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a46b34defa672268474fbb5541f6297f45df9e4ecc4def6edd6fe1c44bfdb795", size = 3080908, upload-time = "2025-05-08T21:21:10.228Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7a/5d5507f1fafd3215ea7d01e1c78809ca71dddd88b4ce75dba3ca53ab2b3b/cython-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8818446612461aca3978ebe8e3def817a120d91f85022540843ebe4f24818cd6", size = 3197585, upload-time = "2025-05-08T21:21:12.354Z" }, + { url = "https://files.pythonhosted.org/packages/f8/97/34dd0112d042ec85736273acb2caced7e576681fbf003bcefd007bbd36d2/cython-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe401e825b0fbeec75f8cc758c8cf32345c673bdb0edaf9585cd43b9d2798824", size = 3244153, upload-time = "2025-05-08T21:21:14.692Z" }, + { url = "https://files.pythonhosted.org/packages/fb/98/77c50a153f4acdf7b48d055c5387edbcd3de1e0d17affca5181cb009f702/cython-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c96908b302e87e99915b3b66481a976e32b864e95bf054dcd2cb859dffd8cb10", size = 3123440, upload-time = "2025-05-08T21:21:16.873Z" }, + { url = "https://files.pythonhosted.org/packages/63/22/ecb9301ee1efbb0b3a6be371fd01be87d45113ed2fe804897d399da8af6f/cython-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cdde5f25fdb8a5d50dbe5d418fe5bfb2260b1acdbd45b788e77b247e9adf2f56", size = 3337569, upload-time = "2025-05-08T21:21:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/fa/67/b707c2548cc2f790c60bb634b996f93e748ad081681c28fe7a22591e663e/cython-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe3320d13cde70fa8b1936e633b9e0fa68720cc61f97aa371d56d0f84fba3e02", size = 3289049, upload-time = "2025-05-08T21:21:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/be/61/05b58fc96248b2d6e487f1f8d20217d3ce6d4ba5b621fb315b23c87d077c/cython-3.1.0-cp312-cp312-win32.whl", hash = "sha256:d41d17d7cfcfbddf3b7dc0ceddb6361b8e749b0b3c5f8efa40c31c249127fa15", size = 2469164, upload-time = "2025-05-08T21:21:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a1/74009b116dbec7d6c721ac268a4aa2c0c0069c99c138586f27c8fb77cae3/cython-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:61eb67401bd6c977084fc789812bd40f96be500049adb2bab99921d696ae0c87", size = 2673707, upload-time = "2025-05-08T21:21:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/221af506254978b119a2f6769b81b16bcfe09e0fb3fc5ab66e53e536d933/cython-3.1.0-py3-none-any.whl", hash = "sha256:4e460bdf1d8742ddf4914959842f2f23ca4934df97f864be799ddf1912acd0ab", size = 1227398, upload-time = "2025-05-08T20:25:31.368Z" }, ] [[package]] @@ -503,7 +507,7 @@ version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, @@ -538,27 +542,27 @@ wheels = [ [[package]] name = "fonttools" -version = "4.57.0" +version = "4.58.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/2d/a9a0b6e3a0cf6bd502e64fc16d894269011930cabfc89aee20d1635b1441/fonttools-4.57.0.tar.gz", hash = "sha256:727ece10e065be2f9dd239d15dd5d60a66e17eac11aea47d447f9f03fdbc42de", size = 3492448, upload-time = "2025-04-03T11:07:13.898Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/4d037663e2a1fe30fddb655d755d76e18624be44ad467c07412c2319ab97/fonttools-4.58.0.tar.gz", hash = "sha256:27423d0606a2c7b336913254bf0b1193ebd471d5f725d665e875c5e88a011a43", size = 3514522, upload-time = "2025-05-10T17:36:35.886Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/1f/e67c99aa3c6d3d2f93d956627e62a57ae0d35dc42f26611ea2a91053f6d6/fonttools-4.57.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3871349303bdec958360eedb619169a779956503ffb4543bb3e6211e09b647c4", size = 2757392, upload-time = "2025-04-03T11:05:45.715Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f1/f75770d0ddc67db504850898d96d75adde238c35313409bfcd8db4e4a5fe/fonttools-4.57.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c59375e85126b15a90fcba3443eaac58f3073ba091f02410eaa286da9ad80ed8", size = 2285609, upload-time = "2025-04-03T11:05:47.977Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d3/bc34e4953cb204bae0c50b527307dce559b810e624a733351a654cfc318e/fonttools-4.57.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967b65232e104f4b0f6370a62eb33089e00024f2ce143aecbf9755649421c683", size = 4873292, upload-time = "2025-04-03T11:05:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/b8/d5933559303a4ab18c799105f4c91ee0318cc95db4a2a09e300116625e7a/fonttools-4.57.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39acf68abdfc74e19de7485f8f7396fa4d2418efea239b7061d6ed6a2510c746", size = 4902503, upload-time = "2025-04-03T11:05:52.17Z" }, - { url = "https://files.pythonhosted.org/packages/32/13/acb36bfaa316f481153ce78de1fa3926a8bad42162caa3b049e1afe2408b/fonttools-4.57.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d077f909f2343daf4495ba22bb0e23b62886e8ec7c109ee8234bdbd678cf344", size = 5077351, upload-time = "2025-04-03T11:05:54.162Z" }, - { url = "https://files.pythonhosted.org/packages/b5/23/6d383a2ca83b7516d73975d8cca9d81a01acdcaa5e4db8579e4f3de78518/fonttools-4.57.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:46370ac47a1e91895d40e9ad48effbe8e9d9db1a4b80888095bc00e7beaa042f", size = 5275067, upload-time = "2025-04-03T11:05:57.375Z" }, - { url = "https://files.pythonhosted.org/packages/bc/ca/31b8919c6da0198d5d522f1d26c980201378c087bdd733a359a1e7485769/fonttools-4.57.0-cp311-cp311-win32.whl", hash = "sha256:ca2aed95855506b7ae94e8f1f6217b7673c929e4f4f1217bcaa236253055cb36", size = 2158263, upload-time = "2025-04-03T11:05:59.567Z" }, - { url = "https://files.pythonhosted.org/packages/13/4c/de2612ea2216eb45cfc8eb91a8501615dd87716feaf5f8fb65cbca576289/fonttools-4.57.0-cp311-cp311-win_amd64.whl", hash = "sha256:17168a4670bbe3775f3f3f72d23ee786bd965395381dfbb70111e25e81505b9d", size = 2204968, upload-time = "2025-04-03T11:06:02.16Z" }, - { url = "https://files.pythonhosted.org/packages/cb/98/d4bc42d43392982eecaaca117d79845734d675219680cd43070bb001bc1f/fonttools-4.57.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:889e45e976c74abc7256d3064aa7c1295aa283c6bb19810b9f8b604dfe5c7f31", size = 2751824, upload-time = "2025-04-03T11:06:03.782Z" }, - { url = "https://files.pythonhosted.org/packages/1a/62/7168030eeca3742fecf45f31e63b5ef48969fa230a672216b805f1d61548/fonttools-4.57.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0425c2e052a5f1516c94e5855dbda706ae5a768631e9fcc34e57d074d1b65b92", size = 2283072, upload-time = "2025-04-03T11:06:05.533Z" }, - { url = "https://files.pythonhosted.org/packages/5d/82/121a26d9646f0986ddb35fbbaf58ef791c25b59ecb63ffea2aab0099044f/fonttools-4.57.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44c26a311be2ac130f40a96769264809d3b0cb297518669db437d1cc82974888", size = 4788020, upload-time = "2025-04-03T11:06:07.249Z" }, - { url = "https://files.pythonhosted.org/packages/5b/26/e0f2fb662e022d565bbe280a3cfe6dafdaabf58889ff86fdef2d31ff1dde/fonttools-4.57.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c41ba992df5b8d680b89fd84c6a1f2aca2b9f1ae8a67400c8930cd4ea115f6", size = 4859096, upload-time = "2025-04-03T11:06:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/9e/44/9075e323347b1891cdece4b3f10a3b84a8f4c42a7684077429d9ce842056/fonttools-4.57.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea1e9e43ca56b0c12440a7c689b1350066595bebcaa83baad05b8b2675129d98", size = 4964356, upload-time = "2025-04-03T11:06:11.294Z" }, - { url = "https://files.pythonhosted.org/packages/48/28/caa8df32743462fb966be6de6a79d7f30393859636d7732e82efa09fbbb4/fonttools-4.57.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:84fd56c78d431606332a0627c16e2a63d243d0d8b05521257d77c6529abe14d8", size = 5226546, upload-time = "2025-04-03T11:06:13.6Z" }, - { url = "https://files.pythonhosted.org/packages/f6/46/95ab0f0d2e33c5b1a4fc1c0efe5e286ba9359602c0a9907adb1faca44175/fonttools-4.57.0-cp312-cp312-win32.whl", hash = "sha256:f4376819c1c778d59e0a31db5dc6ede854e9edf28bbfa5b756604727f7f800ac", size = 2146776, upload-time = "2025-04-03T11:06:15.643Z" }, - { url = "https://files.pythonhosted.org/packages/06/5d/1be5424bb305880e1113631f49a55ea7c7da3a5fe02608ca7c16a03a21da/fonttools-4.57.0-cp312-cp312-win_amd64.whl", hash = "sha256:57e30241524879ea10cdf79c737037221f77cc126a8cdc8ff2c94d4a522504b9", size = 2193956, upload-time = "2025-04-03T11:06:17.534Z" }, - { url = "https://files.pythonhosted.org/packages/90/27/45f8957c3132917f91aaa56b700bcfc2396be1253f685bd5c68529b6f610/fonttools-4.57.0-py3-none-any.whl", hash = "sha256:3122c604a675513c68bd24c6a8f9091f1c2376d18e8f5fe5a101746c81b3e98f", size = 1093605, upload-time = "2025-04-03T11:07:11.341Z" }, + { url = "https://files.pythonhosted.org/packages/76/2e/9b9bd943872a50cb182382f8f4a99af92d76e800603d5f73e4343fdce61a/fonttools-4.58.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9345b1bb994476d6034996b31891c0c728c1059c05daa59f9ab57d2a4dce0f84", size = 2751920, upload-time = "2025-05-10T17:35:16.487Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8c/e8d6375da893125f610826c2e30e6d2597dfb8dad256f8ff5a54f3089fda/fonttools-4.58.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1d93119ace1e2d39ff1340deb71097932f72b21c054bd3da727a3859825e24e5", size = 2313957, upload-time = "2025-05-10T17:35:18.906Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1b/a29cb00c8c20164b24f88780e298fafd0bbfb25cf8bc7b10c4b69331ad5d/fonttools-4.58.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79c9e4f01bb04f19df272ae35314eb6349fdb2e9497a163cd22a21be999694bd", size = 4913808, upload-time = "2025-05-10T17:35:21.394Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ab/9b9507b65b15190cbfe1ccd3c08067d79268d8312ef20948b16d9f5aa905/fonttools-4.58.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62ecda1465d38248aaf9bee1c17a21cf0b16aef7d121d7d303dbb320a6fd49c2", size = 4935876, upload-time = "2025-05-10T17:35:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/15/e4/1395853bc775b0ab06a1c61cf261779afda7baff3f65cf1197bbd21aa149/fonttools-4.58.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:29d0499bff12a26733c05c1bfd07e68465158201624b2fba4a40b23d96c43f94", size = 4974798, upload-time = "2025-05-10T17:35:26.189Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b9/0358368ef5462f4653a198207b29885bee8d5e23c870f6125450ed88e693/fonttools-4.58.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1871abdb0af582e2d96cc12d88889e3bfa796928f491ec14d34a2e58ca298c7e", size = 5093560, upload-time = "2025-05-10T17:35:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/11/00/f64bc3659980c41eccf2c371e62eb15b40858f02a41a0e9c6258ef094388/fonttools-4.58.0-cp311-cp311-win32.whl", hash = "sha256:e292485d70402093eb94f6ab7669221743838b8bd4c1f45c84ca76b63338e7bf", size = 2186330, upload-time = "2025-05-10T17:35:31.733Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a0/0287be13a1ec7733abf292ffbd76417cea78752d4ce10fecf92d8b1252d6/fonttools-4.58.0-cp311-cp311-win_amd64.whl", hash = "sha256:6df3755fcf9ad70a74ad3134bd5c9738f73c9bb701a304b1c809877b11fe701c", size = 2234687, upload-time = "2025-05-10T17:35:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4e/1c6b35ec7c04d739df4cf5aace4b7ec284d6af2533a65de21972e2f237d9/fonttools-4.58.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa8316798f982c751d71f0025b372151ea36405733b62d0d94d5e7b8dd674fa6", size = 2737502, upload-time = "2025-05-10T17:35:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/fc/72/c6fcafa3c9ed2b69991ae25a1ba7a3fec8bf74928a96e8229c37faa8eda2/fonttools-4.58.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c6db489511e867633b859b11aefe1b7c0d90281c5bdb903413edbb2ba77b97f1", size = 2307214, upload-time = "2025-05-10T17:35:38.939Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/1015cedc9878da6d8d1758049749eef857b693e5828d477287a959c8650f/fonttools-4.58.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:107bdb2dacb1f627db3c4b77fb16d065a10fe88978d02b4fc327b9ecf8a62060", size = 4811136, upload-time = "2025-05-10T17:35:41.491Z" }, + { url = "https://files.pythonhosted.org/packages/32/b9/6a1bc1af6ec17eead5d32e87075e22d0dab001eace0b5a1542d38c6a9483/fonttools-4.58.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba7212068ab20f1128a0475f169068ba8e5b6e35a39ba1980b9f53f6ac9720ac", size = 4876598, upload-time = "2025-05-10T17:35:43.986Z" }, + { url = "https://files.pythonhosted.org/packages/d8/46/b14584c7ea65ad1609fb9632251016cda8a2cd66b15606753b9f888d3677/fonttools-4.58.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f95ea3b6a3b9962da3c82db73f46d6a6845a6c3f3f968f5293b3ac1864e771c2", size = 4872256, upload-time = "2025-05-10T17:35:46.617Z" }, + { url = "https://files.pythonhosted.org/packages/05/78/b2105a7812ca4ef9bf180cd741c82f4522316c652ce2a56f788e2eb54b62/fonttools-4.58.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:874f1225cc4ccfeac32009887f722d7f8b107ca5e867dcee067597eef9d4c80b", size = 5028710, upload-time = "2025-05-10T17:35:49.227Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a9/a38c85ffd30d1f2c7a5460c8abfd1aa66e00c198df3ff0b08117f5c6fcd9/fonttools-4.58.0-cp312-cp312-win32.whl", hash = "sha256:5f3cde64ec99c43260e2e6c4fa70dfb0a5e2c1c1d27a4f4fe4618c16f6c9ff71", size = 2173593, upload-time = "2025-05-10T17:35:51.226Z" }, + { url = "https://files.pythonhosted.org/packages/66/48/29752962a74b7ed95da976b5a968bba1fe611a4a7e50b9fefa345e6e7025/fonttools-4.58.0-cp312-cp312-win_amd64.whl", hash = "sha256:2aee08e2818de45067109a207cbd1b3072939f77751ef05904d506111df5d824", size = 2223230, upload-time = "2025-05-10T17:35:53.653Z" }, + { url = "https://files.pythonhosted.org/packages/9b/1f/4417c26e26a1feab85a27e927f7a73d8aabc84544be8ba108ce4aa90eb1e/fonttools-4.58.0-py3-none-any.whl", hash = "sha256:c96c36880be2268be409df7b08c5b5dacac1827083461a6bc2cb07b8cbcec1d7", size = 1111440, upload-time = "2025-05-10T17:36:33.607Z" }, ] [[package]] @@ -650,10 +654,10 @@ name = "gymnasium" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "farama-notifications", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326, upload-time = "2025-03-06T16:30:36.428Z" } wheels = [ @@ -921,7 +925,7 @@ wheels = [ [[package]] name = "matplotlib" -version = "3.10.1" +version = "3.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -934,20 +938,20 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/08/b89867ecea2e305f408fbb417139a8dd941ecf7b23a2e02157c36da546f0/matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba", size = 36743335, upload-time = "2025-02-27T19:19:51.038Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/14/a1b840075be247bb1834b22c1e1d558740b0f618fe3a823740181ca557a1/matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401", size = 8174669, upload-time = "2025-02-27T19:18:34.346Z" }, - { url = "https://files.pythonhosted.org/packages/0a/e4/300b08e3e08f9c98b0d5635f42edabf2f7a1d634e64cb0318a71a44ff720/matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe", size = 8047996, upload-time = "2025-02-27T19:18:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/75/f9/8d99ff5a2498a5f1ccf919fb46fb945109623c6108216f10f96428f388bc/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd", size = 8461612, upload-time = "2025-02-27T19:18:39.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/53fa08a5eaf78d3a7213fd6da1feec4bae14a81d9805e567013811ff0e85/matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c", size = 8602258, upload-time = "2025-02-27T19:18:43.217Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/4397d2ce808467af86684a622dd112664553e81752ea8bf61bdd89d24a41/matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7", size = 9408896, upload-time = "2025-02-27T19:18:45.852Z" }, - { url = "https://files.pythonhosted.org/packages/d7/68/0d03098b3feb786cbd494df0aac15b571effda7f7cbdec267e8a8d398c16/matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a", size = 8061281, upload-time = "2025-02-27T19:18:48.919Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/5e0dc3b59c034e43de16f94deb68f4ad8a96b3ea00f4b37c160b7474928e/matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107", size = 8175488, upload-time = "2025-02-27T19:18:51.436Z" }, - { url = "https://files.pythonhosted.org/packages/7a/81/dae7e14042e74da658c3336ab9799128e09a1ee03964f2d89630b5d12106/matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be", size = 8046264, upload-time = "2025-02-27T19:18:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/21/c4/22516775dcde10fc9c9571d155f90710761b028fc44f660508106c363c97/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6", size = 8452048, upload-time = "2025-02-27T19:18:56.536Z" }, - { url = "https://files.pythonhosted.org/packages/63/23/c0615001f67ce7c96b3051d856baedc0c818a2ed84570b9bf9bde200f85d/matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d", size = 8597111, upload-time = "2025-02-27T19:18:59.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/c0/a07939a82aed77770514348f4568177d7dadab9787ebc618a616fe3d665e/matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea", size = 9402771, upload-time = "2025-02-27T19:19:01.944Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b6/a9405484fb40746fdc6ae4502b16a9d6e53282ba5baaf9ebe2da579f68c4/matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c", size = 8063742, upload-time = "2025-02-27T19:19:04.632Z" }, + { url = "https://files.pythonhosted.org/packages/f5/bd/af9f655456f60fe1d575f54fb14704ee299b16e999704817a7645dfce6b0/matplotlib-3.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0ef061f74cd488586f552d0c336b2f078d43bc00dc473d2c3e7bfee2272f3fa8", size = 8178873, upload-time = "2025-05-08T19:09:53.857Z" }, + { url = "https://files.pythonhosted.org/packages/c2/86/e1c86690610661cd716eda5f9d0b35eaf606ae6c9b6736687cfc8f2d0cd8/matplotlib-3.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96985d14dc5f4a736bbea4b9de9afaa735f8a0fc2ca75be2fa9e96b2097369d", size = 8052205, upload-time = "2025-05-08T19:09:55.684Z" }, + { url = "https://files.pythonhosted.org/packages/54/51/a9f8e49af3883dacddb2da1af5fca1f7468677f1188936452dd9aaaeb9ed/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5f0283da91e9522bdba4d6583ed9d5521566f63729ffb68334f86d0bb98049", size = 8465823, upload-time = "2025-05-08T19:09:57.442Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e3/c82963a3b86d6e6d5874cbeaa390166458a7f1961bab9feb14d3d1a10f02/matplotlib-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdfa07c0ec58035242bc8b2c8aae37037c9a886370eef6850703d7583e19964b", size = 8606464, upload-time = "2025-05-08T19:09:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/0e/34/24da1027e7fcdd9e82da3194c470143c551852757a4b473a09a012f5b945/matplotlib-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c0b9849a17bce080a16ebcb80a7b714b5677d0ec32161a2cc0a8e5a6030ae220", size = 9413103, upload-time = "2025-05-08T19:10:03.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/da/948a017c3ea13fd4a97afad5fdebe2f5bbc4d28c0654510ce6fd6b06b7bd/matplotlib-3.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:eef6ed6c03717083bc6d69c2d7ee8624205c29a8e6ea5a31cd3492ecdbaee1e1", size = 8065492, upload-time = "2025-05-08T19:10:05.271Z" }, + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, ] [[package]] @@ -964,22 +968,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "gymnasium", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "lxml", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "matplotlib", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "progressbar", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "psutil", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pygments", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "requests", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "shapely", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "tqdm", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "filelock" }, + { name = "gymnasium" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "panda3d" }, + { name = "panda3d-gltf" }, + { name = "pillow" }, + { name = "progressbar" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "requests" }, + { name = "shapely" }, + { name = "tqdm" }, + { name = "yapf" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" }, @@ -1238,7 +1242,7 @@ name = "opencv-python-headless" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } wheels = [ @@ -1450,8 +1454,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "panda3d-simplepbr" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573, upload-time = "2021-05-21T05:46:32.738Z" } wheels = [ @@ -1463,8 +1467,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/be/c4d1ded04c22b357277cf6e6a44c1ab4abb285a700bd1991460460e05b99/panda3d_simplepbr-0.13.1.tar.gz", hash = "sha256:c83766d7c8f47499f365a07fe1dff078fc8b3054c2689bdc8dceabddfe7f1a35", size = 6216055, upload-time = "2025-03-30T16:57:41.087Z" } wheels = [ @@ -1528,11 +1532,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload-time = "2025-03-19T20:36:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload-time = "2025-03-19T20:36:09.038Z" }, + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] [[package]] @@ -1862,162 +1866,162 @@ name = "pyobjc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-addressbook", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applescriptkit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-automator", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-carbon", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cfnetwork", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudiokit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremidi", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecordingui", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-diskarbitration", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-dvdplayback", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-exceptionhandling", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-installerplugins", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetoothui", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-latentsemanticmapping", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-launchservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-osakit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-preferencepanes", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screensaver", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-searchkit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-securityfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-securityinterface", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-syncservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-systemconfiguration", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-addressbook" }, + { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-applescriptkit" }, + { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-applicationservices" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automator" }, + { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, + { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-carbon" }, + { name = "pyobjc-framework-cfnetwork" }, + { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coreaudiokit" }, + { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremidi" }, + { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-discrecording" }, + { name = "pyobjc-framework-discrecordingui" }, + { name = "pyobjc-framework-diskarbitration" }, + { name = "pyobjc-framework-dvdplayback" }, + { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-exceptionhandling" }, + { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-installerplugins" }, + { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-iobluetooth" }, + { name = "pyobjc-framework-iobluetoothui" }, + { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-latentsemanticmapping" }, + { name = "pyobjc-framework-launchservices" }, + { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-osakit" }, + { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-preferencepanes" }, + { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, + { name = "pyobjc-framework-screensaver" }, + { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-searchkit" }, + { name = "pyobjc-framework-security" }, + { name = "pyobjc-framework-securityfoundation" }, + { name = "pyobjc-framework-securityinterface" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-syncservices" }, + { name = "pyobjc-framework-systemconfiguration" }, + { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-webkit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/d6/27b1c9a02f6cb4954984ce1a0239618e52f78c329c7e7450bf1f219b0f0a/pyobjc-11.0.tar.gz", hash = "sha256:a8f7baed65797f67afd46290b02f652c23f4b158ddf960bce0441b78f6803418", size = 11044, upload-time = "2025-01-14T19:02:12.55Z" } wheels = [ @@ -2039,9 +2043,9 @@ name = "pyobjc-framework-accessibility" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/61/7484cc4ad3aa7854cd4c969379a5f044261259d08f7c20b6718493b484f9/pyobjc_framework_accessibility-11.0.tar.gz", hash = "sha256:097450c641fa9ac665199762e77867f2a82775be2f749b8fa69223b828f60656", size = 44597, upload-time = "2025-01-14T19:02:17.596Z" } wheels = [ @@ -2054,8 +2058,8 @@ name = "pyobjc-framework-accounts" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c2/fa/b64f3f02e0a8b189dc07c391546e2dbe30ef1b3515d1427cdab743545b90/pyobjc_framework_accounts-11.0.tar.gz", hash = "sha256:afc4ae277be1e3e1f90269001c2fd886093a5465e365d7f9a3a0af3e17f06210", size = 17340, upload-time = "2025-01-14T19:02:18.625Z" } wheels = [ @@ -2068,8 +2072,8 @@ name = "pyobjc-framework-addressbook" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/ef/5b5f6b61907ae43509fbf1654e043115d9a64d97efdc28fbb90d06c199f6/pyobjc_framework_addressbook-11.0.tar.gz", hash = "sha256:87073c85bb342eb27faa6eceb7a0e8a4c1e32ad1f2b62bb12dafb5e7b9f15837", size = 97116, upload-time = "2025-01-14T19:02:19.527Z" } wheels = [ @@ -2082,8 +2086,8 @@ name = "pyobjc-framework-adservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/51/7c/0c6e01f83b0c5c7968564a40146f4d07080df278457bdb5a982c8f26a74d/pyobjc_framework_adservices-11.0.tar.gz", hash = "sha256:d2e1a2f395e93e1bbe754ab0d76ce1d64c0d3928472634437e0382eafc6765cd", size = 12732, upload-time = "2025-01-14T19:02:20.559Z" } wheels = [ @@ -2096,8 +2100,8 @@ name = "pyobjc-framework-adsupport" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/07/b8b5f741d1e2cad97100444b255e6ecaca3668e7414039981799aa330035/pyobjc_framework_adsupport-11.0.tar.gz", hash = "sha256:20eb8a683d34fb7a6efeceaf964a24b88c3434875c44f66db5e1b609e678043a", size = 12819, upload-time = "2025-01-14T19:02:23.032Z" } wheels = [ @@ -2110,8 +2114,8 @@ name = "pyobjc-framework-applescriptkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/c3/d7f9a33de7ab8e3950350e0862214e66f27ed6bff1a491bc391c377ab83e/pyobjc_framework_applescriptkit-11.0.tar.gz", hash = "sha256:4bafac4a036f0fb8ba01488b8e91d3ac861ce6e61154ffbd0b26f82b99779b50", size = 12638, upload-time = "2025-01-14T19:02:25.1Z" } wheels = [ @@ -2124,8 +2128,8 @@ name = "pyobjc-framework-applescriptobjc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/9f/bb4fdbcea418f8472d7a67d4d2e4a15fca11fed04648db5208b0fce84807/pyobjc_framework_applescriptobjc-11.0.tar.gz", hash = "sha256:baff9988b6e886aed0e76441358417707de9088be5733f22055fed7904ca1001", size = 12675, upload-time = "2025-01-14T19:02:25.947Z" } wheels = [ @@ -2138,10 +2142,10 @@ name = "pyobjc-framework-applicationservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/fb/4e42573b0d3baa3fa18ec53614cf979f951313f1451e8f2e17df9429da1f/pyobjc_framework_applicationservices-11.0.tar.gz", hash = "sha256:d6ea18dfc7d5626a3ecf4ac72d510405c0d3a648ca38cae8db841acdebecf4d2", size = 224334, upload-time = "2025-01-14T19:02:26.828Z" } wheels = [ @@ -2154,8 +2158,8 @@ name = "pyobjc-framework-apptrackingtransparency" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/40/c1c48ed49b5e55c7a635aa1e7ca41ffa1c5547e26243f26489c4768cd730/pyobjc_framework_apptrackingtransparency-11.0.tar.gz", hash = "sha256:cd5c834b5b19c21ad6c317ba5d29f30a8d0ae5d14e7cf557da22abc0850f1e91", size = 13385, upload-time = "2025-01-14T19:02:29.226Z" } wheels = [ @@ -2168,8 +2172,8 @@ name = "pyobjc-framework-audiovideobridging" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/5f/0bd5beded0415b53f443da804410eda6a53e1bc64f8779ed9a592719da8c/pyobjc_framework_audiovideobridging-11.0.tar.gz", hash = "sha256:dbc45b06418dd780c365956fdfd69d007436b5ee54c51e671196562eb8290ba6", size = 72418, upload-time = "2025-01-14T19:02:30.083Z" } wheels = [ @@ -2182,8 +2186,8 @@ name = "pyobjc-framework-authenticationservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/0f/2de0d941e9c9b2eb1ce8b22eb31adc7227badfe1e53f615431d3a7fdcd48/pyobjc_framework_authenticationservices-11.0.tar.gz", hash = "sha256:6a060ce651df142e8923d1383449bc6f2c7f5eb0b517152dac609bde3901064e", size = 140036, upload-time = "2025-01-14T19:02:31.115Z" } wheels = [ @@ -2196,8 +2200,8 @@ name = "pyobjc-framework-automaticassessmentconfiguration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/09/d5/5febfee260b88e426c7e799cc95990818feeaa9f740fb9dd516559c96520/pyobjc_framework_automaticassessmentconfiguration-11.0.tar.gz", hash = "sha256:5d3691af2b94e44ca594b6791556e15a9f0a3f9432df51cb891f5f859a65e467", size = 24420, upload-time = "2025-01-14T19:02:32.101Z" } wheels = [ @@ -2210,8 +2214,8 @@ name = "pyobjc-framework-automator" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/1b/1ba4eb296c3915f2e367e45470cb310a9c78b4dd65a37bd522f458f245aa/pyobjc_framework_automator-11.0.tar.gz", hash = "sha256:412d330f8c6f30066cad15e1bdecdc865510bbce469cc7d9477384c4e9f2550f", size = 200905, upload-time = "2025-01-14T19:02:33.039Z" } wheels = [ @@ -2224,11 +2228,11 @@ name = "pyobjc-framework-avfoundation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/06/018ad0e2a38dbdbc5c126d7ce37488c4d581d4e2a2b9ef678162bb36d5f6/pyobjc_framework_avfoundation-11.0.tar.gz", hash = "sha256:269a592bdaf8a16948d8935f0cf7c8cb9a53e7ea609a963ada0e55f749ddb530", size = 871064, upload-time = "2025-01-14T19:02:35.757Z" } wheels = [ @@ -2241,9 +2245,9 @@ name = "pyobjc-framework-avkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/79/5b2fcb94b051da32a24b54bb0d90b1d01b190e1402b6303747de47fb17ac/pyobjc_framework_avkit-11.0.tar.gz", hash = "sha256:5fa40919320277b820df3e4c6e84cba91ef7221a28f4eb5374e3dbd80d1e521a", size = 46311, upload-time = "2025-01-14T19:02:37.018Z" } wheels = [ @@ -2256,8 +2260,8 @@ name = "pyobjc-framework-avrouting" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/80/63680dc7788bc3573a20fc5421dfcf606970a0cd3b2457829d9b66603ae0/pyobjc_framework_avrouting-11.0.tar.gz", hash = "sha256:54ec9ea0b5adb5149b554e23c07c6b4f4bdb2892ca2ed7b3e88a5de936313025", size = 20561, upload-time = "2025-01-14T19:02:38.157Z" } wheels = [ @@ -2270,8 +2274,8 @@ name = "pyobjc-framework-backgroundassets" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/17/83b873069b0c0763365de88648ad4a2472e9e96fcac39fa534f3633552e8/pyobjc_framework_backgroundassets-11.0.tar.gz", hash = "sha256:9488c3f86bf427898a88b7100e77200c08a487a35c75c1b5735bd69c57ba38cb", size = 23658, upload-time = "2025-01-14T19:02:42.665Z" } wheels = [ @@ -2284,11 +2288,11 @@ name = "pyobjc-framework-browserenginekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/2e/df3d2f7e53132d398c2922d331dd1d2aa352997a1a4a1390e59db51c1d13/pyobjc_framework_browserenginekit-11.0.tar.gz", hash = "sha256:51971527f5103c0e09a4ef438c352ebb037fcad8971f8420a781c72ee421f758", size = 31352, upload-time = "2025-01-14T19:02:45.499Z" } wheels = [ @@ -2301,8 +2305,8 @@ name = "pyobjc-framework-businesschat" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/f2/4541989f2c9c5fc3cdfc94ebf31fc6619554b6c22dafdbb57f866a392bc1/pyobjc_framework_businesschat-11.0.tar.gz", hash = "sha256:20fe1c8c848ef3c2e132172d9a007a8aa65b08875a9ca5c27afbfc4396b16dbb", size = 12953, upload-time = "2025-01-14T19:02:46.378Z" } wheels = [ @@ -2315,8 +2319,8 @@ name = "pyobjc-framework-calendarstore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/d3/722c1b16c7d9bdd5c408735c15193e8396f2d22ab6410b0af4569f39c46e/pyobjc_framework_calendarstore-11.0.tar.gz", hash = "sha256:40173f729df56b70ec14f9680962a248c3ce7b4babb46e8b0d760a13975ef174", size = 68475, upload-time = "2025-01-14T19:02:48.544Z" } wheels = [ @@ -2329,8 +2333,8 @@ name = "pyobjc-framework-callkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/0a/9d39ebac92006960b8059f664d8eb7b9cdb8763fe4e8102b2d24b853004f/pyobjc_framework_callkit-11.0.tar.gz", hash = "sha256:52e44a05d0357558e1479977ed2bcb325fabc8d337f641f0249178b5b491fc59", size = 39720, upload-time = "2025-01-14T19:02:50.697Z" } wheels = [ @@ -2343,8 +2347,8 @@ name = "pyobjc-framework-carbon" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/15/51964f36a8ae1002b16d213d2e5ba11cc861bdd9369f1e3f116350d788c5/pyobjc_framework_carbon-11.0.tar.gz", hash = "sha256:476f690f0b34aa9e4cb3923e61481aefdcf33e38ec6087b530a94871eee2b914", size = 37538, upload-time = "2025-01-14T19:02:51.62Z" } wheels = [ @@ -2357,8 +2361,8 @@ name = "pyobjc-framework-cfnetwork" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4f/36/7cebdfb621c7d46eeab3173256bc2e1cba1bbbbe6c0ac8aeb9a4fe2a4627/pyobjc_framework_cfnetwork-11.0.tar.gz", hash = "sha256:eb742fc6a42b248886ff09c3cf247d56e65236864bbea4264e70af8377948d96", size = 78532, upload-time = "2025-01-14T19:02:52.777Z" } wheels = [ @@ -2371,11 +2375,11 @@ name = "pyobjc-framework-cinematic" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/ef/b5857d567cd6e0366f61c381ebea52383b98d1ac03341f39e779a085812a/pyobjc_framework_cinematic-11.0.tar.gz", hash = "sha256:94a2de8bf3f38bd190311b6bf98d1e2cea7888840b3ce3aa92e464c0216a5cdb", size = 25740, upload-time = "2025-01-14T19:02:54.95Z" } wheels = [ @@ -2388,8 +2392,8 @@ name = "pyobjc-framework-classkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/81/126075eaf5ccf254ddb4cfd99d92a266c30803c5b4572ea3a920fd85e850/pyobjc_framework_classkit-11.0.tar.gz", hash = "sha256:dc5b3856612cafdc7071fbebc252b8908dbf2433e0e5ddb15a0bcd1ee282d27c", size = 39301, upload-time = "2025-01-14T19:02:55.779Z" } wheels = [ @@ -2402,11 +2406,11 @@ name = "pyobjc-framework-cloudkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accounts", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accounts" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corelocation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/6c/b0709fed7fc5a1e81de311b9273bb7ba3820a636f8ba880e90510bb6d460/pyobjc_framework_cloudkit-11.0.tar.gz", hash = "sha256:e3f6bf2c3358dd394174b1e69fcec6859951fcd15f6433c6fa3082e3b7e2656d", size = 123034, upload-time = "2025-01-14T19:02:56.769Z" } wheels = [ @@ -2419,7 +2423,7 @@ name = "pyobjc-framework-cocoa" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/32/53809096ad5fc3e7a2c5ddea642590a5f2cb5b81d0ad6ea67fdb2263d9f9/pyobjc_framework_cocoa-11.0.tar.gz", hash = "sha256:00346a8cb81ad7b017b32ff7bf596000f9faa905807b1bd234644ebd47f692c5", size = 6173848, upload-time = "2025-01-14T19:03:00.125Z" } wheels = [ @@ -2432,8 +2436,8 @@ name = "pyobjc-framework-collaboration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/ee/1f6893eb882af5ecc6a6f4182b2ec85df777c4bc6b9a20a6b42c23abff3f/pyobjc_framework_collaboration-11.0.tar.gz", hash = "sha256:9f53929dd6d5b1a5511494432bf83807041c6f8b9ab6cf6ff184eee0b6f8226f", size = 17084, upload-time = "2025-01-14T19:03:01.98Z" } wheels = [ @@ -2446,8 +2450,8 @@ name = "pyobjc-framework-colorsync" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/24/397a80cd2313cc9e1b73b9acb1de66b740bbece4fe87ed4ea158de8fcef8/pyobjc_framework_colorsync-11.0.tar.gz", hash = "sha256:4f531f6075d9cc4b9d426620a1b04d3aaeb56b5ff178d0a6b0e93d068a5db0d2", size = 39249, upload-time = "2025-01-14T19:03:02.887Z" } wheels = [ @@ -2460,8 +2464,8 @@ name = "pyobjc-framework-contacts" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/a2/89053853b28c1f2f2e69092d3e81b7c26073bc8396fc87772b3b1bfb9d57/pyobjc_framework_contacts-11.0.tar.gz", hash = "sha256:fc215baa9f66dbf9ffa1cb8170d102a3546cfd708b2b42de4e9d43645aec03d9", size = 84253, upload-time = "2025-01-14T19:03:03.743Z" } wheels = [ @@ -2474,9 +2478,9 @@ name = "pyobjc-framework-contactsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contacts", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-contacts" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3f/67/122b16fd7f2da7f0f48c1d7fcaf0f1951253ddd5489d909a1b5fb80f3925/pyobjc_framework_contactsui-11.0.tar.gz", hash = "sha256:d0f2a4afea807fbe4db1518c4f81f0dc9aa1817fe7cb16115308fc00375a70db", size = 19486, upload-time = "2025-01-14T19:03:04.72Z" } wheels = [ @@ -2489,8 +2493,8 @@ name = "pyobjc-framework-coreaudio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/e6/3b7a8af3defec012d6cacf277fd8d5c3e254ceace63a05447dc1119f3a7e/pyobjc_framework_coreaudio-11.0.tar.gz", hash = "sha256:38b6b531381119be6998cf704d04c9ea475aaa33f6dd460e0584351475acd0ae", size = 140507, upload-time = "2025-01-14T19:03:05.612Z" } wheels = [ @@ -2503,9 +2507,9 @@ name = "pyobjc-framework-coreaudiokit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/1a/604cac8d992b6e66adbb98edb1f65820116f5d74d8decd6d43898ae2929d/pyobjc_framework_coreaudiokit-11.0.tar.gz", hash = "sha256:1a4c3de4a02b0dfa7410c012c7f0939edd2e127d439fb934aeafc68450615f1d", size = 21450, upload-time = "2025-01-14T19:03:06.681Z" } wheels = [ @@ -2518,8 +2522,8 @@ name = "pyobjc-framework-corebluetooth" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/74/66a62a36da9db5924ee15de6fe1eb544930609b307b3bfbc021b5cf43781/pyobjc_framework_corebluetooth-11.0.tar.gz", hash = "sha256:1dcb7c039c2efa7c72dc14cdda80e677240b49fa38999941a77ee02ca142998d", size = 59797, upload-time = "2025-01-14T19:03:07.584Z" } wheels = [ @@ -2532,8 +2536,8 @@ name = "pyobjc-framework-coredata" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/22/6787205b91cb6d526b6b472ebaa5baff275200774050a55b4b25d2bd957a/pyobjc_framework_coredata-11.0.tar.gz", hash = "sha256:b11acb51ff31cfb69a53f4e127996bf194bcac770e8fa67cb5ba3fb16a496058", size = 260029, upload-time = "2025-01-14T19:03:08.609Z" } wheels = [ @@ -2546,8 +2550,8 @@ name = "pyobjc-framework-corehaptics" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/b8/66481497362171e7ad42fc8fcc0272c04b95a707c5c1e7e8f8a8bfe58917/pyobjc_framework_corehaptics-11.0.tar.gz", hash = "sha256:1949b56ac0bd4219eb04c466cdd0f7f93d6826ed92ee61f01a4b5e98139ee039", size = 42956, upload-time = "2025-01-14T19:03:09.753Z" } wheels = [ @@ -2560,8 +2564,8 @@ name = "pyobjc-framework-corelocation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/2d/b21ca49a34db49390420a9d7d05fd9eb89850dbec0a555c9ee408f52609c/pyobjc_framework_corelocation-11.0.tar.gz", hash = "sha256:05055c3b567f7f8f796845da43fb755d84d630909b927a39f25cf706ef52687d", size = 103955, upload-time = "2025-01-14T19:03:10.707Z" } wheels = [ @@ -2574,8 +2578,8 @@ name = "pyobjc-framework-coremedia" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/60/7c7b9f13c94910882de6cc08f48a52cce9739e75cc3b3b6de5c857e6536a/pyobjc_framework_coremedia-11.0.tar.gz", hash = "sha256:a414db97ba30b43c9dd96213459d6efb169f9e92ce1ad7a75516a679b181ddfb", size = 249161, upload-time = "2025-01-14T19:03:12.291Z" } wheels = [ @@ -2588,8 +2592,8 @@ name = "pyobjc-framework-coremediaio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/59/904af57d302caa4c20d3bfebb9fb9300ccc3c396134460821c9f1e8ab65b/pyobjc_framework_coremediaio-11.0.tar.gz", hash = "sha256:7d652cf1a2a75c78ea6e8dbc7fc8b782bfc0f07eafc84b700598172c82f373d8", size = 107856, upload-time = "2025-01-14T19:03:14.225Z" } wheels = [ @@ -2602,8 +2606,8 @@ name = "pyobjc-framework-coremidi" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/90/d004cdf4c52b8b16842e15135495de882d743b4f0217946bd8ae1a920173/pyobjc_framework_coremidi-11.0.tar.gz", hash = "sha256:acace4448b3e4802ab5dd75bbf875aae5e1f6c8cab2b2f1d58af20fc8b2a5a7f", size = 107342, upload-time = "2025-01-14T19:03:15.235Z" } wheels = [ @@ -2616,8 +2620,8 @@ name = "pyobjc-framework-coreml" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/64/4f0a990ec0955fe9b88f1fa58303c8471c551996670216527b4ac559ed8f/pyobjc_framework_coreml-11.0.tar.gz", hash = "sha256:143a1f73a0ea0a0ea103f3175cb87a61bbcb98f70f85320ed4c61302b9156d58", size = 81452, upload-time = "2025-01-14T19:03:16.283Z" } wheels = [ @@ -2630,8 +2634,8 @@ name = "pyobjc-framework-coremotion" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/79/5c4ff39a48f0dc0f764d1330b2360e9f31e3a32414e8690e7f20e4574e93/pyobjc_framework_coremotion-11.0.tar.gz", hash = "sha256:d1e7ca418897e35365d07c6fd5b5d625a3c44261b6ce46dcf80787f634ad6fa5", size = 66508, upload-time = "2025-01-14T19:03:17.254Z" } wheels = [ @@ -2644,9 +2648,9 @@ name = "pyobjc-framework-coreservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fsevents", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-fsevents" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/b5/19c096b9938d6e2fdb1b436f21ad989b77dbeb4e59b3db4bd344800fa1e8/pyobjc_framework_coreservices-11.0.tar.gz", hash = "sha256:ac96954f1945a1153bdfef685611665749eaa8016b5af6f34bd56a274952b03a", size = 1244406, upload-time = "2025-01-14T19:03:19.202Z" } wheels = [ @@ -2659,8 +2663,8 @@ name = "pyobjc-framework-corespotlight" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/6a/6707d7ef339b9ad2dd0994d1df42969ee3b231f2d098f3377d40aed60b4f/pyobjc_framework_corespotlight-11.0.tar.gz", hash = "sha256:a96c9b4ba473bc3ee19afa01a9af989458e6a56e9656c2cdea1850d2b13720e6", size = 86130, upload-time = "2025-01-14T19:03:20.457Z" } wheels = [ @@ -2673,9 +2677,9 @@ name = "pyobjc-framework-coretext" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/e8/9b68dc788828e38143a3e834e66346713751cb83d7f0955016323005c1a2/pyobjc_framework_coretext-11.0.tar.gz", hash = "sha256:a68437153e627847e3898754dd3f13ae0cb852246b016a91f9c9cbccb9f91a43", size = 274222, upload-time = "2025-01-14T19:03:21.521Z" } wheels = [ @@ -2688,8 +2692,8 @@ name = "pyobjc-framework-corewlan" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/a9/cda522b270adb75d62bae447b2131da62912b5eda058a07e3a433689116f/pyobjc_framework_corewlan-11.0.tar.gz", hash = "sha256:8803981d64e3eb4fa0ea56657a9b98e4004de5a84d56e32e5444815d8ed6fa6f", size = 65254, upload-time = "2025-01-14T19:03:23.938Z" } wheels = [ @@ -2702,8 +2706,8 @@ name = "pyobjc-framework-cryptotokenkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/72/b871fa5476479e4a22a4a0e971fb4724b0eb94c721365539ad55f4dc3135/pyobjc_framework_cryptotokenkit-11.0.tar.gz", hash = "sha256:a1bbfe9170c35cb427d39167af55aefea651c5c8a45c0de60226dae04b61a6b1", size = 58734, upload-time = "2025-01-14T19:03:24.851Z" } wheels = [ @@ -2716,8 +2720,8 @@ name = "pyobjc-framework-datadetection" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/6b/b896feb16e914dc81b6ed6cdbd0b6e6390eaafc80fff5297ec17eb0bd716/pyobjc_framework_datadetection-11.0.tar.gz", hash = "sha256:9967555151892f8400cffac86e8656f2cb8d7866963fdee255e0747fa1386533", size = 13738, upload-time = "2025-01-14T19:03:27.054Z" } wheels = [ @@ -2730,8 +2734,8 @@ name = "pyobjc-framework-devicecheck" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/f8/237a92dd9ba8a88b7027f78cba83e61b0011bfc2a49351ecaa177233f639/pyobjc_framework_devicecheck-11.0.tar.gz", hash = "sha256:66cff0323dc8eef1b76d60f9c9752684f11e534ebda60ecbf6858a9c73553f64", size = 14198, upload-time = "2025-01-14T19:03:27.918Z" } wheels = [ @@ -2744,8 +2748,8 @@ name = "pyobjc-framework-devicediscoveryextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/48/178a1879109128f34334fdae2fe4463c7620f169593bea96704f347d945e/pyobjc_framework_devicediscoveryextension-11.0.tar.gz", hash = "sha256:576dac3f418cfc4f71020a45f06231d14e4b2a8e182ef0020dd9da3cf238d02f", size = 14511, upload-time = "2025-01-14T19:03:32.132Z" } wheels = [ @@ -2758,8 +2762,8 @@ name = "pyobjc-framework-dictionaryservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/cf/2913c7df737eb8519acb7ef6429127e40d6c334415e38cfa18d6481150eb/pyobjc_framework_dictionaryservices-11.0.tar.gz", hash = "sha256:6b5f27c75424860f169e7c7e182fabffdba22854fedb8023de180e8770661dce", size = 10823, upload-time = "2025-01-14T19:03:32.942Z" } wheels = [ @@ -2772,8 +2776,8 @@ name = "pyobjc-framework-discrecording" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/cc/f36612b67ca1fff7659d7933b563dce61f8c84dad0bf79fab08bb34949ad/pyobjc_framework_discrecording-11.0.tar.gz", hash = "sha256:6bdc533f067d049ea5032f65af70b5cdab68673574ac32dacb46509a9411d256", size = 122426, upload-time = "2025-01-14T19:03:35.589Z" } wheels = [ @@ -2786,9 +2790,9 @@ name = "pyobjc-framework-discrecordingui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-discrecording" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/6b/3c120c59a939854dd4b7a162fad47011375c5ba00a12940f7217aea90eeb/pyobjc_framework_discrecordingui-11.0.tar.gz", hash = "sha256:bec8a252fd2022dce6c58b1f3366a7295efb0c7c77817f11f9efcce70527d7a2", size = 19614, upload-time = "2025-01-14T19:03:36.695Z" } wheels = [ @@ -2801,8 +2805,8 @@ name = "pyobjc-framework-diskarbitration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/fb/5d3ff093144f499904b1e1bce18d010fe2171b9be62b4679d3dda8b3ad19/pyobjc_framework_diskarbitration-11.0.tar.gz", hash = "sha256:1c3e21398b366a1ce96cf68501a2e415f5ccad4b43a3e7cc901e09e896dfb545", size = 20096, upload-time = "2025-01-14T19:03:37.659Z" } wheels = [ @@ -2815,8 +2819,8 @@ name = "pyobjc-framework-dvdplayback" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/89/89ebee4863fd6f173bff9373b5bda4ffa87eba6197337617ab086e23c7d5/pyobjc_framework_dvdplayback-11.0.tar.gz", hash = "sha256:9a005f441afbc34aea301857e166fd650d82762a75d024253e18d1102b21b2f8", size = 64798, upload-time = "2025-01-14T19:03:38.491Z" } wheels = [ @@ -2829,8 +2833,8 @@ name = "pyobjc-framework-eventkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/13/38a98e5cee62e1655d84cfb88cad54fdec4ec272b5fd0c5ac3fc21e33e49/pyobjc_framework_eventkit-11.0.tar.gz", hash = "sha256:3d412203a510b3d62a5eb0987406e0951b13ed39c3351c0ec874afd72496627c", size = 75399, upload-time = "2025-01-14T19:03:39.441Z" } wheels = [ @@ -2843,8 +2847,8 @@ name = "pyobjc-framework-exceptionhandling" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/46/6c2c4805697a0cfb8413eb7bc6901298e7a1febd49bb1ea960274fc33af3/pyobjc_framework_exceptionhandling-11.0.tar.gz", hash = "sha256:b11562c6eeaef5d8d43e9d817cf50feceb02396e5eb6a7f61df2c0cec93d912b", size = 18157, upload-time = "2025-01-14T19:03:40.393Z" } wheels = [ @@ -2857,8 +2861,8 @@ name = "pyobjc-framework-executionpolicy" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/91/2e4cacbdabf01bc1207817edacc814b6bc486df12e857a8d86964d98fef4/pyobjc_framework_executionpolicy-11.0.tar.gz", hash = "sha256:de953a8acae98079015b19e75ec8154a311ac1a70fb6d885e17fab09464c98a9", size = 13753, upload-time = "2025-01-14T19:03:42.353Z" } wheels = [ @@ -2871,8 +2875,8 @@ name = "pyobjc-framework-extensionkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/98/803e3cb000dac227eb0d223802a0aeb052d34a741e572d9584e7d83afca7/pyobjc_framework_extensionkit-11.0.tar.gz", hash = "sha256:82d9e79532e5a0ff0eadf1ccac236c5d3dca344e1090a0f3e88519faa24143c7", size = 19200, upload-time = "2025-01-14T19:03:43.188Z" } wheels = [ @@ -2885,8 +2889,8 @@ name = "pyobjc-framework-externalaccessory" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/b0/ac0a02fe26e66c33fee751a65c1ed06bbd2934db8636e08bb491e8334bad/pyobjc_framework_externalaccessory-11.0.tar.gz", hash = "sha256:39e59331ced75cdcccf23bb5ffe0fa9d67e0c190c1da8887a0e4349b7e27584f", size = 22577, upload-time = "2025-01-14T19:03:44.021Z" } wheels = [ @@ -2899,8 +2903,8 @@ name = "pyobjc-framework-fileprovider" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/fc/b8593d8645b9933e60a885f451d0c12d9c0e1b00e62121d8660d95852dff/pyobjc_framework_fileprovider-11.0.tar.gz", hash = "sha256:dcc3ac3c90117c1b8027ea5f26dad6fe5045f688ce3e60d07ece12ec56e17ab3", size = 78701, upload-time = "2025-01-14T19:03:44.931Z" } wheels = [ @@ -2913,8 +2917,8 @@ name = "pyobjc-framework-fileproviderui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-fileprovider" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/9d/ca4aed36e6188623e9da633634af772f239bee74934322e1c19ae7b79a53/pyobjc_framework_fileproviderui-11.0.tar.gz", hash = "sha256:cf5c7d32b29d344b65217397eea7b1a2913ce52ce923c9e04135a7a298848d04", size = 13419, upload-time = "2025-01-14T19:03:46.016Z" } wheels = [ @@ -2927,8 +2931,8 @@ name = "pyobjc-framework-findersync" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/e3/24df6e24b589073815be13f2943b93feb12afbf558f6e54c4033b57c29ee/pyobjc_framework_findersync-11.0.tar.gz", hash = "sha256:8dab3feff5debd6bc3746a21ded991716723d98713d1ba37cec1c5e2ad78ee63", size = 15295, upload-time = "2025-01-14T19:03:46.91Z" } wheels = [ @@ -2941,8 +2945,8 @@ name = "pyobjc-framework-fsevents" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/37/4c09cc7b8678e2bb5b68ebc62e817eb88c409b1c41bdc1510d7d24a0372d/pyobjc_framework_fsevents-11.0.tar.gz", hash = "sha256:e01dab04704a518e4c3e1f7d8722819a4f228d5082978e11618aa7abba3883fe", size = 29078, upload-time = "2025-01-14T19:03:49.762Z" } wheels = [ @@ -2955,8 +2959,8 @@ name = "pyobjc-framework-gamecenter" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7f/3b/e66caebc948d9fe3b2671659caab220aff6d5e80ac25442d83331b523d23/pyobjc_framework_gamecenter-11.0.tar.gz", hash = "sha256:18a05500dbcf2cca4a0f05839ec010c76ee08ab65b65020c9538a31feb274483", size = 31459, upload-time = "2025-01-14T19:03:50.766Z" } wheels = [ @@ -2969,8 +2973,8 @@ name = "pyobjc-framework-gamecontroller" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/30/02ca5a4fb911acf3e8018abcbd29631a842aeac02958ae91fab1acb13ad1/pyobjc_framework_gamecontroller-11.0.tar.gz", hash = "sha256:6d62f4493d634eba03a43a14c4d1e4511e1e3a2ca2e9cbefa6ae9278a272c1d0", size = 115318, upload-time = "2025-01-14T19:03:52.264Z" } wheels = [ @@ -2983,9 +2987,9 @@ name = "pyobjc-framework-gamekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3f/df/c161460e5736a34f9b59aa0a3f2d6ad1d1cd9a913aa63c89c41a6ba3b6ae/pyobjc_framework_gamekit-11.0.tar.gz", hash = "sha256:29b5464ca78f0de62e6b6d56e80bbeccb96dc13820b6d5b4e835ab1cc127e5b9", size = 164394, upload-time = "2025-01-14T19:03:53.762Z" } wheels = [ @@ -2998,9 +3002,9 @@ name = "pyobjc-framework-gameplaykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-spritekit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-spritekit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/f0/980c4fc3c594d9726b7eb6ae83f73127b22560e1541c7d272d23d17fdf0d/pyobjc_framework_gameplaykit-11.0.tar.gz", hash = "sha256:90eeec464fba992d75a406ccbddb35ed7420a4f5226f19c018982fa3ba7bf431", size = 72837, upload-time = "2025-01-14T19:03:56.127Z" } wheels = [ @@ -3013,8 +3017,8 @@ name = "pyobjc-framework-healthkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/2f/d79d2ec7c23bfc94bfaa7b7c6f6487a8bffdb73263eea6900aab56135889/pyobjc_framework_healthkit-11.0.tar.gz", hash = "sha256:e78ccb05f747ae3e70b5d73522030b7ba01ef2d390155fba7d50c1c614ae241f", size = 201558, upload-time = "2025-01-14T19:03:57.117Z" } wheels = [ @@ -3027,8 +3031,8 @@ name = "pyobjc-framework-imagecapturecore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/fe/db1fc3ffd784a9010070cd87a05d7fd2542c400395589341fab5970a01e1/pyobjc_framework_imagecapturecore-11.0.tar.gz", hash = "sha256:f5d185d8c8b564f8b4a815381bcdb424b10d203ba5bdf0fc887085e007df6f7a", size = 99935, upload-time = "2025-01-14T19:03:58.548Z" } wheels = [ @@ -3041,8 +3045,8 @@ name = "pyobjc-framework-inputmethodkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/e9/13d007285582e598903264a7d25cc6771a2a52d6c2a96a68fe91db0844fb/pyobjc_framework_inputmethodkit-11.0.tar.gz", hash = "sha256:86cd648bf98c4e777c884b7f69ebcafba84866740430d297645bf388eee6ce52", size = 26684, upload-time = "2025-01-14T19:03:59.525Z" } wheels = [ @@ -3055,8 +3059,8 @@ name = "pyobjc-framework-installerplugins" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f2/f3/0379655e8ea3566002768d5e7b3ccd72ca845390632a8dabf801348af3a7/pyobjc_framework_installerplugins-11.0.tar.gz", hash = "sha256:88ec84e6999e8b2df874758b09878504a4fbfc8471cf3cd589d57e556f5b916e", size = 27687, upload-time = "2025-01-14T19:04:00.515Z" } wheels = [ @@ -3069,9 +3073,9 @@ name = "pyobjc-framework-instantmessage" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/4d/6810a1f2039ff24d9498858b3ebb46357d4091aa5cec9ff4e41bbcdb25de/pyobjc_framework_instantmessage-11.0.tar.gz", hash = "sha256:ec5c4c70c9b0e61ae82888067246e4f931e700d625b3c42604e54759d4fbf65c", size = 34027, upload-time = "2025-01-14T19:04:01.405Z" } wheels = [ @@ -3084,8 +3088,8 @@ name = "pyobjc-framework-intents" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/88/07e47b0c5c46fe97c23c883ae7a053c2ca6f6fd6afe851d1c2c784644f0f/pyobjc_framework_intents-11.0.tar.gz", hash = "sha256:6405c816dfed8ffa8b3f8b0fae75f61d64787dbae8db1c475bb4450cf8fdf6b5", size = 447921, upload-time = "2025-01-14T19:04:02.487Z" } wheels = [ @@ -3098,8 +3102,8 @@ name = "pyobjc-framework-intentsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-intents" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/96/3b3b367f70a4d0a60d2c6251e4a1f4bf470945ae939e0ba20e6d56d10c7a/pyobjc_framework_intentsui-11.0.tar.gz", hash = "sha256:4ce04f926c823fbc1fba7d9c5b33d512b514396719e6bc50ef65b82774e42bc5", size = 20774, upload-time = "2025-01-14T19:04:03.648Z" } wheels = [ @@ -3112,8 +3116,8 @@ name = "pyobjc-framework-iobluetooth" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1e/46/62913f8e5ac307b154b3dd50a7a0b167c9d7ac2a579223e33208c141c387/pyobjc_framework_iobluetooth-11.0.tar.gz", hash = "sha256:869f01f573482da92674abbae4a154143e993b1fe4b2c3523f9e0f9c48b798d4", size = 300463, upload-time = "2025-01-14T19:04:04.582Z" } wheels = [ @@ -3126,8 +3130,8 @@ name = "pyobjc-framework-iobluetoothui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-iobluetooth" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/55/d194de8cfa63c96970e6c90c35e80ce3fceb42934a85d3728736a0e416ff/pyobjc_framework_iobluetoothui-11.0.tar.gz", hash = "sha256:a583758d3e54149ee2dcf00374685aa99e8ae407e044f7c378acc002f9f27e63", size = 23091, upload-time = "2025-01-14T19:04:05.659Z" } wheels = [ @@ -3140,8 +3144,8 @@ name = "pyobjc-framework-iosurface" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/91/ae9ca9e1a777eb786d9d43649437d01d24386736cffe9bb2f504b57e8db6/pyobjc_framework_iosurface-11.0.tar.gz", hash = "sha256:24da8d1cf9356717b1c7e75a1c61e9a9417b62f051d13423a4a7b0978d3dcda5", size = 20555, upload-time = "2025-01-14T19:04:09.475Z" } wheels = [ @@ -3154,8 +3158,8 @@ name = "pyobjc-framework-ituneslibrary" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/fe/881ab1058d795fe68ccc1e14df0d5e161601dced15d3be84105ecc44bae6/pyobjc_framework_ituneslibrary-11.0.tar.gz", hash = "sha256:2e15dcfbb9d5e95634ddff153de159a28f5879f1a13fdf95504e011773056c6e", size = 47647, upload-time = "2025-01-14T19:04:11.333Z" } wheels = [ @@ -3168,8 +3172,8 @@ name = "pyobjc-framework-kernelmanagement" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/ea/8ef534fce78817fc577f18de2b34e363873f785894f2bbbfc694823f5088/pyobjc_framework_kernelmanagement-11.0.tar.gz", hash = "sha256:812479d5f85eae27aeeaa22f64c20b926b28b5b9b2bf31c8eab9496d3e038028", size = 12794, upload-time = "2025-01-14T19:04:14.204Z" } wheels = [ @@ -3182,8 +3186,8 @@ name = "pyobjc-framework-latentsemanticmapping" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/29/8838eefeb82da95931134b06624364812dedf7e9cc905f36d95d497f2904/pyobjc_framework_latentsemanticmapping-11.0.tar.gz", hash = "sha256:6f578c3e0a171706bdbfcfc2c572a8059bf8039d22c1475df13583749a35cec1", size = 17704, upload-time = "2025-01-14T19:04:14.972Z" } wheels = [ @@ -3196,8 +3200,8 @@ name = "pyobjc-framework-launchservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/59/eb847389224c670c885ae3d008b1ffe3b996bbe094b43e49dfa84f3947a9/pyobjc_framework_launchservices-11.0.tar.gz", hash = "sha256:7c5c8a8cec013e2cb3fa82a167ca2d61505c36a79f75c718f3f913e597f9ffee", size = 20691, upload-time = "2025-01-14T19:04:15.884Z" } wheels = [ @@ -3210,8 +3214,8 @@ name = "pyobjc-framework-libdispatch" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/33/4ec96a9edd37948f09e94635852c2db695141430cc1adc7b25968e1f3a95/pyobjc_framework_libdispatch-11.0.tar.gz", hash = "sha256:d22df11b07b1c3c8e7cfc4ba9e876a95c19f44acd36cf13d40c5cccc1ffda04b", size = 53496, upload-time = "2025-01-14T19:04:16.82Z" } wheels = [ @@ -3224,8 +3228,8 @@ name = "pyobjc-framework-libxpc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/7e/9fa73ce6925db9cfd8a6b45d97943af8fe59f92251e7fd201b6e4608c172/pyobjc_framework_libxpc-11.0.tar.gz", hash = "sha256:e0c336913ab6a526b036915aa9038de2a5281e696ac2d3db3347b3040519c11d", size = 48627, upload-time = "2025-01-14T19:04:17.728Z" } wheels = [ @@ -3238,9 +3242,9 @@ name = "pyobjc-framework-linkpresentation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/5c/dac9fe4ad0a4076c863b5ac9925e751fc18c637ae411e4891c4b7558a5b3/pyobjc_framework_linkpresentation-11.0.tar.gz", hash = "sha256:bc4ace4aab4da4a4e4df10517bd478b6d51ebf00b423268ee8d9f356f9e87be9", size = 15231, upload-time = "2025-01-14T19:04:20.763Z" } wheels = [ @@ -3253,9 +3257,9 @@ name = "pyobjc-framework-localauthentication" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ec/b1/bea4b5f8adbb69c0b34eddee63e052f35271cc630db43fbef6873352e21f/pyobjc_framework_localauthentication-11.0.tar.gz", hash = "sha256:eb55a3de647894092d6ed3f8f13fdc38e5dbf4850be320ea14dd2ac83176b298", size = 40020, upload-time = "2025-01-14T19:04:22.206Z" } wheels = [ @@ -3268,9 +3272,9 @@ name = "pyobjc-framework-localauthenticationembeddedui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthentication", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-localauthentication" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/ee/821f2d2e9da4cba3dc47e50c8367c6405e91551fb7d8ec842858d5b1d45d/pyobjc_framework_localauthenticationembeddedui-11.0.tar.gz", hash = "sha256:7e9bf6df77ff12a4e827988d8578c15b4431694b2fcfd5b0dad5d7738757ee6a", size = 14204, upload-time = "2025-01-14T19:04:23.566Z" } wheels = [ @@ -3283,8 +3287,8 @@ name = "pyobjc-framework-mailkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/79/9c9140f726ba14898762ddc19e7142724e0ce5930f08eb20f33f78b05be8/pyobjc_framework_mailkit-11.0.tar.gz", hash = "sha256:d08a2dcc95b5e7955c7c385fe6e018325113d02c007c4178d3fb3c9ab326c163", size = 32274, upload-time = "2025-01-14T19:04:25.086Z" } wheels = [ @@ -3297,10 +3301,10 @@ name = "pyobjc-framework-mapkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-corelocation" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/7e/ef86c6e218a58bb9497ce9754a77f12ffe01c4b3609279727b7d7e44655a/pyobjc_framework_mapkit-11.0.tar.gz", hash = "sha256:cd8a91df4c0b442fcf1b14d735e566a06b21b3f48a2a4afe269fca45bfa49117", size = 165080, upload-time = "2025-01-14T19:04:26.606Z" } wheels = [ @@ -3313,8 +3317,8 @@ name = "pyobjc-framework-mediaaccessibility" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/8e/9fe2cb251ff6107a03bafa07f63b6593df145a2579fffb096023fb21b167/pyobjc_framework_mediaaccessibility-11.0.tar.gz", hash = "sha256:1298cc0128e1c0724e8f8e63a6167ea6809a985922c67399b997f8243de59ab4", size = 18671, upload-time = "2025-01-14T19:04:27.624Z" } wheels = [ @@ -3327,10 +3331,10 @@ name = "pyobjc-framework-mediaextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/1f/e31d9431bc71077b09583ea863b3c91b7de9371d0cc17a8be99be8119daa/pyobjc_framework_mediaextension-11.0.tar.gz", hash = "sha256:ecd8a64939e1c16be005690117c21fd406fc04d3036e2adea7600d2a0c53f4ea", size = 57931, upload-time = "2025-01-14T19:04:28.65Z" } wheels = [ @@ -3343,9 +3347,9 @@ name = "pyobjc-framework-medialibrary" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/a4/8c7d1635994800dc412a5db2c4b43ed499184651efcec0c8da3cf8e2bcc7/pyobjc_framework_medialibrary-11.0.tar.gz", hash = "sha256:692889fab1e479a9c207f0ff23c900dad5f47caf47c05cc995d9bb7c1e56e8b9", size = 18975, upload-time = "2025-01-14T19:04:29.739Z" } wheels = [ @@ -3358,8 +3362,8 @@ name = "pyobjc-framework-mediaplayer" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/3d2783f2f96ddf51bebcf6537a4a0f2a8a1fe4e520de218fc1b7c5b219ed/pyobjc_framework_mediaplayer-11.0.tar.gz", hash = "sha256:c61be0ba6c648db6b1d013a52f9afb8901a8d7fbabd983df2175c1b1fbff81e5", size = 94020, upload-time = "2025-01-14T19:04:30.617Z" } wheels = [ @@ -3372,8 +3376,8 @@ name = "pyobjc-framework-mediatoolbox" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/46/cf5f3bde6cad32f10095850ca44f24ba241d18b26379187c412be1260f39/pyobjc_framework_mediatoolbox-11.0.tar.gz", hash = "sha256:de949a44f10b5a15e5a7131ee53b2806b8cb753fd01a955970ec0f475952ba24", size = 23067, upload-time = "2025-01-14T19:04:32.823Z" } wheels = [ @@ -3386,8 +3390,8 @@ name = "pyobjc-framework-metal" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/e0/a6d18a1183410a5d8610ca1ae6c065b8944586441f8669faee7509817246/pyobjc_framework_metal-11.0.tar.gz", hash = "sha256:cad390150aa63502d5cfe242026b55ed39ffaf816342ddf51e44a9aead6c24be", size = 446102, upload-time = "2025-01-14T19:04:34.011Z" } wheels = [ @@ -3400,8 +3404,8 @@ name = "pyobjc-framework-metalfx" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/cf/ff9367e4737a12ebd12a17e693ec247028cf065761acc073ebefb2b2393a/pyobjc_framework_metalfx-11.0.tar.gz", hash = "sha256:2ae41991bf7a733c44fcd5b6550cedea3accaaf0f529643975d3da113c9f0caa", size = 26436, upload-time = "2025-01-14T19:04:36.161Z" } wheels = [ @@ -3414,9 +3418,9 @@ name = "pyobjc-framework-metalkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/92/27/fb3c1b10914abf2ae6682837abf76bcd8cb7af2ba613fbc55fb9d055bb95/pyobjc_framework_metalkit-11.0.tar.gz", hash = "sha256:1bbbe35c7c6a481383d32f6eaae59a1cd8084319a65c1aa343d63a257d8b4ddb", size = 44628, upload-time = "2025-01-14T19:04:36.977Z" } wheels = [ @@ -3429,8 +3433,8 @@ name = "pyobjc-framework-metalperformanceshaders" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/c2/c08996a8c6cfef09fb9e726cc99b0bf3ad0ffcef66d5c2543e6b35dd4e2e/pyobjc_framework_metalperformanceshaders-11.0.tar.gz", hash = "sha256:41179e3a11e55325153fffd84f48946d47c1dc1944677febd871a127021e056d", size = 301444, upload-time = "2025-01-14T19:04:38.064Z" } wheels = [ @@ -3443,8 +3447,8 @@ name = "pyobjc-framework-metalperformanceshadersgraph" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metalperformanceshaders" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/b8/353852c76eb437e907ca0acf8a5b5f9255e9b9ee8c0706b69b0c17498f97/pyobjc_framework_metalperformanceshadersgraph-11.0.tar.gz", hash = "sha256:33077ebbbe1aa7787de2552a83534be6c439d7f4272de17915a85fda8fd3b72d", size = 105381, upload-time = "2025-01-14T19:04:39.831Z" } wheels = [ @@ -3457,8 +3461,8 @@ name = "pyobjc-framework-metrickit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/82/605ad654f40ff4480ba9366ad3726da80c98e33b73f122fb91259be1ce81/pyobjc_framework_metrickit-11.0.tar.gz", hash = "sha256:ee3da403863beec181a2d6dc7b7eeb4d07e954b88bbabac58a82523b2f83fdc7", size = 40414, upload-time = "2025-01-14T19:04:41.186Z" } wheels = [ @@ -3471,8 +3475,8 @@ name = "pyobjc-framework-mlcompute" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/c9/22fe4720685724ec1444c8e5cdb41d360b1434d0971fb3e43cf3e9bf51fd/pyobjc_framework_mlcompute-11.0.tar.gz", hash = "sha256:1a1ee9ab43d1824300055ff94b042a26f38f1d18f6f0aa08be1c88278e7284d9", size = 89265, upload-time = "2025-01-14T19:04:43.326Z" } wheels = [ @@ -3485,9 +3489,9 @@ name = "pyobjc-framework-modelio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/7c/b75b84d41e7854ffe9c9a42846f8105227a5fd0b02b690b4a75018b2caa3/pyobjc_framework_modelio-11.0.tar.gz", hash = "sha256:c875eb6ff7f94d18362a00faaa3016ae0c28140326338d18aa03c0b62f1c6b9d", size = 122652, upload-time = "2025-01-14T19:04:44.263Z" } wheels = [ @@ -3500,8 +3504,8 @@ name = "pyobjc-framework-multipeerconnectivity" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/80/4137cb9751aa3846c4954b3e61f948aae17afeb6851e01194aa50683caef/pyobjc_framework_multipeerconnectivity-11.0.tar.gz", hash = "sha256:8278a3483c0b6b88a8888ca76c46fd85808f9df56d45708cbc4e4182a5565cd3", size = 25534, upload-time = "2025-01-14T19:04:45.211Z" } wheels = [ @@ -3514,8 +3518,8 @@ name = "pyobjc-framework-naturallanguage" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/64/63e97635fa637384bc8c980796573dc7a9e7074a6866aef073b1faf3e11d/pyobjc_framework_naturallanguage-11.0.tar.gz", hash = "sha256:4c9471fa2c48a8fd4899de4406823e66cb0292dbba7b471622017f3647d53fa4", size = 46385, upload-time = "2025-01-14T19:04:46.185Z" } wheels = [ @@ -3528,8 +3532,8 @@ name = "pyobjc-framework-netfs" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/29/eb569870b52c7581104ed2806cae2d425d60b5ab304128cd58155d5b567f/pyobjc_framework_netfs-11.0.tar.gz", hash = "sha256:3de5f627a62addf4aab8a4d2d07213e9b2b6c8adbe6cc4c332ee868075785a6a", size = 16173, upload-time = "2025-01-14T19:04:47.11Z" } wheels = [ @@ -3542,8 +3546,8 @@ name = "pyobjc-framework-network" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/8e/18e55aff83549e041484d2ee94dd91b29cec9de40508e7fe9c4afec110a7/pyobjc_framework_network-11.0.tar.gz", hash = "sha256:d4dcc02773d7d642a385c7f0d951aeb7361277446c912a49230cddab60a65ab8", size = 124160, upload-time = "2025-01-14T19:04:50.191Z" } wheels = [ @@ -3556,8 +3560,8 @@ name = "pyobjc-framework-networkextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/90/97dcfac5895b07e891adf634c3a074b68992d132ccfab386c186ac1a598c/pyobjc_framework_networkextension-11.0.tar.gz", hash = "sha256:5ba2254e2c13010b6c4f1e2948047d95eff86bfddfc77716747718fa3a8cb1af", size = 188551, upload-time = "2025-01-14T19:04:51.352Z" } wheels = [ @@ -3570,8 +3574,8 @@ name = "pyobjc-framework-notificationcenter" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/d0/f0a602e01173531a2b639e283a092cf1f307fd873abd2ed590b9c4122337/pyobjc_framework_notificationcenter-11.0.tar.gz", hash = "sha256:f878b318c693d63d6b8bd1c3e2ad4f8097b22872f18f40142e394d84f1ead9f6", size = 22844, upload-time = "2025-01-14T19:04:52.459Z" } wheels = [ @@ -3584,8 +3588,8 @@ name = "pyobjc-framework-opendirectory" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/cf/ba0cf807758acdc6a19e4787fdcda2eb59034aa22c4203d04fd49b276981/pyobjc_framework_opendirectory-11.0.tar.gz", hash = "sha256:0c82594f4f0bcf2318c4641527f9243962d7b03e67d4f3fb111b899a299fc7eb", size = 189165, upload-time = "2025-01-14T19:04:53.42Z" } wheels = [ @@ -3598,8 +3602,8 @@ name = "pyobjc-framework-osakit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d3/4a/e49680f7f3ab9c0632ed9be76a0a59299e7fd797335690b3da4d117f2d7b/pyobjc_framework_osakit-11.0.tar.gz", hash = "sha256:77ac18e2660133a9eeb01c76ad3df3b4b36fd29005fc36bca00f57cca121aac3", size = 22535, upload-time = "2025-01-14T19:04:54.753Z" } wheels = [ @@ -3612,10 +3616,10 @@ name = "pyobjc-framework-oslog" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b0/93/0a72353d0212a815bd5e43aec528ce7b28b71d461d26e5fa3882ff96ffa3/pyobjc_framework_oslog-11.0.tar.gz", hash = "sha256:9d29eb7c89a41d7c702dffb6e2e338a2d5219387c8dae22b67754ddf9e2fcb3f", size = 24151, upload-time = "2025-01-14T19:04:55.587Z" } wheels = [ @@ -3628,8 +3632,8 @@ name = "pyobjc-framework-passkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/f8/ebb2bc840f87292a4f60080463ee698ca08516cc958364741dfff2858b33/pyobjc_framework_passkit-11.0.tar.gz", hash = "sha256:2044d9d634dd98b7b624ee09487b27e5f26a7729f6689abba23a4a011febe19c", size = 120495, upload-time = "2025-01-14T19:04:57.689Z" } wheels = [ @@ -3642,8 +3646,8 @@ name = "pyobjc-framework-pencilkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/8d/1e97cd72b776e5e1294cbda84325b364702617dd435d32448dcc0a80bd93/pyobjc_framework_pencilkit-11.0.tar.gz", hash = "sha256:9598c28e83f5b7f091592cc1af2b16f7ae94cf00045d8d14ed2c17cb9e4ffd50", size = 22812, upload-time = "2025-01-14T19:04:58.652Z" } wheels = [ @@ -3656,8 +3660,8 @@ name = "pyobjc-framework-phase" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/a2/65182dcb44fceb2173f4134d6cd4325dfd0731225b621aa2027d2a03d043/pyobjc_framework_phase-11.0.tar.gz", hash = "sha256:e06a0f8308ae4f3731f88b3e1239b7bdfdda3eef97023e3ce972e2f386451d80", size = 59214, upload-time = "2025-01-14T19:04:59.461Z" } wheels = [ @@ -3670,8 +3674,8 @@ name = "pyobjc-framework-photos" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f7/c3/fc755c1f8f411433d7ba2e92f3fe3e7b417e9629675ad6baf94ac8b01e64/pyobjc_framework_photos-11.0.tar.gz", hash = "sha256:cfdfdefb0d560b091425227d5c0e24a40b445b5251ff4d37bd326cd8626b80cd", size = 92122, upload-time = "2025-01-14T19:05:01.804Z" } wheels = [ @@ -3684,8 +3688,8 @@ name = "pyobjc-framework-photosui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/2c/70ac99fb2b7ba14d220c78cf6401c0c7a47992269f85f699220a6a2cff09/pyobjc_framework_photosui-11.0.tar.gz", hash = "sha256:3c65342e31f6109d8229992b2712b29cab1021475969b55f4f215dd97e2a99db", size = 47898, upload-time = "2025-01-14T19:05:02.737Z" } wheels = [ @@ -3698,8 +3702,8 @@ name = "pyobjc-framework-preferencepanes" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/01/81cc46e0a92d15f2b664b2efdcc8fd310acac570c9f63a99d446e0489784/pyobjc_framework_preferencepanes-11.0.tar.gz", hash = "sha256:ee000c351befeb81f4fa678ada85695ca4af07933b6bd9b1947164e16dd0b3e5", size = 26419, upload-time = "2025-01-14T19:05:03.787Z" } wheels = [ @@ -3712,8 +3716,8 @@ name = "pyobjc-framework-pushkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/ab/7fe55ce5b32c434142be026ec27b1801a2d4694b159b502f9ecd568eebf2/pyobjc_framework_pushkit-11.0.tar.gz", hash = "sha256:df9854ed4065c50022863b3c11c2a21c4279b36c2b5c8f08b834174aacb44e81", size = 20816, upload-time = "2025-01-14T19:05:05.468Z" } wheels = [ @@ -3726,8 +3730,8 @@ name = "pyobjc-framework-quartz" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/ad/f00f3f53387c23bbf4e0bb1410e11978cbf87c82fa6baff0ee86f74c5fb6/pyobjc_framework_quartz-11.0.tar.gz", hash = "sha256:3205bf7795fb9ae34747f701486b3db6dfac71924894d1f372977c4d70c3c619", size = 3952463, upload-time = "2025-01-14T19:05:07.931Z" } wheels = [ @@ -3740,9 +3744,9 @@ name = "pyobjc-framework-quicklookthumbnailing" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/a1/35ca40d2d4ab05acbc9766986d482482d466529003711c7b4e52a8df4935/pyobjc_framework_quicklookthumbnailing-11.0.tar.gz", hash = "sha256:40763284bd0f71e6a55803f5234ad9cd8e8dd3aaaf5e1fd204e6c952b3f3530d", size = 16784, upload-time = "2025-01-14T19:05:09.857Z" } wheels = [ @@ -3755,8 +3759,8 @@ name = "pyobjc-framework-replaykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/43/c751c517dbb8ee599a31e59832c01080473c7964b6996ca29906f46c0967/pyobjc_framework_replaykit-11.0.tar.gz", hash = "sha256:e5693589423eb9ad99d63a7395169f97b484a58108321877b0fc27c748344593", size = 25589, upload-time = "2025-01-14T19:05:10.791Z" } wheels = [ @@ -3769,8 +3773,8 @@ name = "pyobjc-framework-safariservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/ec/c9a97b1aa713145cc8c522c4146af06b293cfe1a959a03ee91007949533b/pyobjc_framework_safariservices-11.0.tar.gz", hash = "sha256:dba416bd0ed5f4481bc400bf56ce57e982c19feaae94bc4eb75d8bda9af15b7e", size = 34367, upload-time = "2025-01-14T19:05:12.914Z" } wheels = [ @@ -3783,9 +3787,9 @@ name = "pyobjc-framework-safetykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/30/89bfdbdca93e57b19891ddeff1742b20a2019cdeb2e44902027dce2642e1/pyobjc_framework_safetykit-11.0.tar.gz", hash = "sha256:9ec996a6a8eecada4b9fd1138244bcffea96a37722531f0ec16566049dfd4cdb", size = 20745, upload-time = "2025-01-14T19:05:13.925Z" } wheels = [ @@ -3798,9 +3802,9 @@ name = "pyobjc-framework-scenekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/3f/a2761585399e752bce8275c9d56990d4b83e57b13d06dd98335891176a89/pyobjc_framework_scenekit-11.0.tar.gz", hash = "sha256:c0f37019f8de2a583f66e6d14dfd4ae23c8d8703e93f61c1c91728a21f62cd26", size = 213647, upload-time = "2025-01-14T19:05:15.129Z" } wheels = [ @@ -3813,9 +3817,9 @@ name = "pyobjc-framework-screencapturekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/90/71f10db2f52ea324f82eaccc959442c43d21778cc5b1294c29e1942e635c/pyobjc_framework_screencapturekit-11.0.tar.gz", hash = "sha256:ca2c960e28216e56f33e4ca9b9b1eda12d9c17b719bae727181e8b96f0314c4b", size = 53046, upload-time = "2025-01-14T19:05:16.834Z" } wheels = [ @@ -3828,8 +3832,8 @@ name = "pyobjc-framework-screensaver" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/b6/71c20259a1bfffcb5103be62564006b1bbc21f80180658101e2370683bcb/pyobjc_framework_screensaver-11.0.tar.gz", hash = "sha256:2e4c643624cc0cffeafc535c43faf5f8de8be030307fa8a5bea257845e8af474", size = 23774, upload-time = "2025-01-14T19:05:19.325Z" } wheels = [ @@ -3842,8 +3846,8 @@ name = "pyobjc-framework-screentime" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/a7/ee60ee5b0471a4367eaa1c8a243418874fd48fac5dbdfdd318a653d94aaa/pyobjc_framework_screentime-11.0.tar.gz", hash = "sha256:6dd74dc64be1865346fcff63b8849253697f7ac68d83ee2708019cf3852c1cd7", size = 14398, upload-time = "2025-01-14T19:05:21.547Z" } wheels = [ @@ -3856,8 +3860,8 @@ name = "pyobjc-framework-scriptingbridge" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/f0/592af19047935e44c07ddd1eba4f05aa8eb460ee842f7d5d48501231cd69/pyobjc_framework_scriptingbridge-11.0.tar.gz", hash = "sha256:65e5edd0ea608ae7f01808b963dfa25743315f563705d75c493c2fa7032f88cc", size = 22626, upload-time = "2025-01-14T19:05:22.461Z" } wheels = [ @@ -3870,8 +3874,8 @@ name = "pyobjc-framework-searchkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/15/27/9676327cf7d13346d546325b411a5deaa072bd0fbe733c8aae8a9a00c0e0/pyobjc_framework_searchkit-11.0.tar.gz", hash = "sha256:36f3109e74bc5e6fab60c02be804d5ed1c511ad51ea0d597a6c6a9653573ddf5", size = 31182, upload-time = "2025-01-14T19:05:24.667Z" } wheels = [ @@ -3884,8 +3888,8 @@ name = "pyobjc-framework-security" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/75/4b916bff8c650e387077a35916b7a7d331d5ff03bed7275099d96dcc6cd9/pyobjc_framework_security-11.0.tar.gz", hash = "sha256:ac078bb9cc6762d6f0f25f68325dcd7fe77acdd8c364bf4378868493f06a0758", size = 347059, upload-time = "2025-01-14T19:05:26.17Z" } wheels = [ @@ -3898,9 +3902,9 @@ name = "pyobjc-framework-securityfoundation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/d6/0d817edb11d2bdb0f536059e913191e587f1984e39397bb3341209d92c21/pyobjc_framework_securityfoundation-11.0.tar.gz", hash = "sha256:5ae906ded5dd40046c013a7e0c1f59416abafb4b72bc947b6cd259749745e637", size = 13526, upload-time = "2025-01-14T19:05:27.275Z" } wheels = [ @@ -3913,9 +3917,9 @@ name = "pyobjc-framework-securityinterface" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/88/d7c4942650707fe5b1d3b45b42684f58f2cab7d2772ec74ca96ecef575eb/pyobjc_framework_securityinterface-11.0.tar.gz", hash = "sha256:8843a27cf30a8e4dd6e2cb7702a6d65ad4222429f0ccc6c062537af4683b1c08", size = 37118, upload-time = "2025-01-14T19:05:28.569Z" } wheels = [ @@ -3928,9 +3932,9 @@ name = "pyobjc-framework-sensitivecontentanalysis" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/e4/f1e0f150ae6c6ad7dde9b248f34f324f4f8b1c42260dbf62420f80d79ba9/pyobjc_framework_sensitivecontentanalysis-11.0.tar.gz", hash = "sha256:0f09034688f894c0f4409c16adaf857d78714d55472de4aa2ac40fbd7ba233d6", size = 13060, upload-time = "2025-01-14T19:05:29.655Z" } wheels = [ @@ -3943,8 +3947,8 @@ name = "pyobjc-framework-servicemanagement" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/59/8d38b5cdbcfb57ab842e080436dbd04d5a5d2080e99a2ea1e286cfad12a8/pyobjc_framework_servicemanagement-11.0.tar.gz", hash = "sha256:10b1bbcee3de5bb2b9fc3d6763eb682b7a1d9ddd4bd2c882fece62783cb17885", size = 16882, upload-time = "2025-01-14T19:05:30.537Z" } wheels = [ @@ -3957,8 +3961,8 @@ name = "pyobjc-framework-sharedwithyou" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-sharedwithyoucore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/84/db667061f815537717a6cac891df01a45b65e6feaa2dfa0c9d2e3803a1ef/pyobjc_framework_sharedwithyou-11.0.tar.gz", hash = "sha256:a3a03daac77ad7364ed22109ca90c6cd2dcb7611a96cbdf37d30543ef1579399", size = 33696, upload-time = "2025-01-14T19:05:31.396Z" } wheels = [ @@ -3971,8 +3975,8 @@ name = "pyobjc-framework-sharedwithyoucore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2a/86904cd9cc3bf5cdb9101481e17e67358f39f81ffa0f36768097287e34b3/pyobjc_framework_sharedwithyoucore-11.0.tar.gz", hash = "sha256:3932452677df5d67ea27845ab26ccaaa1d1779196bf16b62c5655f13d822c82d", size = 28877, upload-time = "2025-01-14T19:05:32.283Z" } wheels = [ @@ -3985,8 +3989,8 @@ name = "pyobjc-framework-shazamkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/2a/1f4ad92260860e500cb61119e8e7fe604b0788c32f5b00446b5a56705a2b/pyobjc_framework_shazamkit-11.0.tar.gz", hash = "sha256:cea736cefe90b6bb989d0a8abdc21ef4b3b431b27657abb09d6deb0b2c1bd37a", size = 25172, upload-time = "2025-01-14T19:05:34.497Z" } wheels = [ @@ -3999,8 +4003,8 @@ name = "pyobjc-framework-social" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/56/ed483f85105ef929241ab1a6ed3dbfd0be558bb900e36b274f997db9c869/pyobjc_framework_social-11.0.tar.gz", hash = "sha256:ccedd6eddb6744049467bce19b4ec4f0667ec60552731c02dcbfa8938a3ac798", size = 14806, upload-time = "2025-01-14T19:05:35.394Z" } wheels = [ @@ -4013,8 +4017,8 @@ name = "pyobjc-framework-soundanalysis" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/14/697ca1b76228a96bb459f3cf43234798b05fdf11691202449d98d9d887af/pyobjc_framework_soundanalysis-11.0.tar.gz", hash = "sha256:f541fcd04ec5d7528dd2ae2d873a92a3092e87fb70b8df229c79defb4d807d1a", size = 16789, upload-time = "2025-01-14T19:05:36.576Z" } wheels = [ @@ -4027,8 +4031,8 @@ name = "pyobjc-framework-speech" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/39/e9f0a73243c38d85f8da6a1a2afda73503e2fcc31a72f5479770bceae0c1/pyobjc_framework_speech-11.0.tar.gz", hash = "sha256:92a191c3ecfe7032eea2140ab5dda826a59c7bb84b13a2edb0ebc471a76e6d7b", size = 40620, upload-time = "2025-01-14T19:05:38.391Z" } wheels = [ @@ -4041,9 +4045,9 @@ name = "pyobjc-framework-spritekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/6e/642e64f5b62a7777c784931c7f018788b5620e307907d416c837fd0c4315/pyobjc_framework_spritekit-11.0.tar.gz", hash = "sha256:aa43927e325d4ac253b7c0ec4df95393b0354bd278ebe9871803419d12d1ef80", size = 129851, upload-time = "2025-01-14T19:05:39.709Z" } wheels = [ @@ -4056,8 +4060,8 @@ name = "pyobjc-framework-storekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/69/ca/f4e5a1ff8c98bbbf208639b2bef7bf3b88936bccda1d8ed34aa7d052f589/pyobjc_framework_storekit-11.0.tar.gz", hash = "sha256:ef7e75b28f1fa8b0b6413e64b9d5d78b8ca358fc2477483d2783f688ff8d75e0", size = 75855, upload-time = "2025-01-14T19:05:41.605Z" } wheels = [ @@ -4070,8 +4074,8 @@ name = "pyobjc-framework-symbols" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/92/a20a3d7af3c99e0ea086e43715675160a04b86c1d069bdaeb3acdb015d92/pyobjc_framework_symbols-11.0.tar.gz", hash = "sha256:e3de7736dfb8107f515cfd23f03e874dd9468e88ab076d01d922a73fefb620fa", size = 13682, upload-time = "2025-01-14T19:05:45.727Z" } wheels = [ @@ -4084,9 +4088,9 @@ name = "pyobjc-framework-syncservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/22/642186906f672461bab1d7773b35ef74e432b9789ca2248186b766e9fd3b/pyobjc_framework_syncservices-11.0.tar.gz", hash = "sha256:7867c23895a8289da8d56e962c144c36ed16bd101dc07d05281c55930b142471", size = 57453, upload-time = "2025-01-14T19:05:46.559Z" } wheels = [ @@ -4099,8 +4103,8 @@ name = "pyobjc-framework-systemconfiguration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/70/ebebf311523f436df2407f35d7ce62482c01e530b77aceb3ca6356dcef43/pyobjc_framework_systemconfiguration-11.0.tar.gz", hash = "sha256:06487f0fdd43c6447b5fd3d7f3f59826178d32bcf74f848c5b3ea597191d471d", size = 142949, upload-time = "2025-01-14T19:05:47.466Z" } wheels = [ @@ -4113,8 +4117,8 @@ name = "pyobjc-framework-systemextensions" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/4b/904d818debf6216b7be009d492d998c819bf2f2791bfb75870a952e32cf9/pyobjc_framework_systemextensions-11.0.tar.gz", hash = "sha256:da293c99b428fb7f18a7a1d311b17177f73a20c7ffa94de3f72d760df924255e", size = 22531, upload-time = "2025-01-14T19:05:48.463Z" } wheels = [ @@ -4127,8 +4131,8 @@ name = "pyobjc-framework-threadnetwork" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/17/fc8fde4eeb6697e0a5ba1a306cd62d3a95b53f3334744cd22b87037d8a14/pyobjc_framework_threadnetwork-11.0.tar.gz", hash = "sha256:f5713579380f6fb89c877796de86cb4e98428d7a9cbfebe566fb827ba23b2d8e", size = 13820, upload-time = "2025-01-14T19:05:49.307Z" } wheels = [ @@ -4141,8 +4145,8 @@ name = "pyobjc-framework-uniformtypeidentifiers" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/4f/fd571c1f87d5ee3d86c4d2008806e9623d2662bbc788d9001b3fff35275f/pyobjc_framework_uniformtypeidentifiers-11.0.tar.gz", hash = "sha256:6ae6927a3ed1f0197a8c472226f11f46ccd5ed398b4449613e1d10346d9ed15d", size = 20860, upload-time = "2025-01-14T19:05:50.073Z" } wheels = [ @@ -4155,8 +4159,8 @@ name = "pyobjc-framework-usernotifications" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/f5/ca3e6a7d940b3aca4323e4f5409b14b5d2eb45432158430c584e3800ce4d/pyobjc_framework_usernotifications-11.0.tar.gz", hash = "sha256:7950a1c6a8297f006c26c3d286705ffc2a07061d6e844f1106290572097b872c", size = 54857, upload-time = "2025-01-14T19:05:52.42Z" } wheels = [ @@ -4169,9 +4173,9 @@ name = "pyobjc-framework-usernotificationsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotifications", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-usernotifications" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/e8/f0d50cdc678260a628b92e55b5752155f941c2f72b96fe3f2412a28c5d79/pyobjc_framework_usernotificationsui-11.0.tar.gz", hash = "sha256:d0ec597d189b4d228b0b836474aef318652c1c287b33442a1403c49dc59fdb7f", size = 14369, upload-time = "2025-01-14T19:05:54.498Z" } wheels = [ @@ -4184,8 +4188,8 @@ name = "pyobjc-framework-videosubscriberaccount" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/2e/6a7debd84911a9384b4e7a9cc3f308e3461a00a9d74f33b153bdd872f15f/pyobjc_framework_videosubscriberaccount-11.0.tar.gz", hash = "sha256:163b32f361f48b9d20f317461464abd4427b3242693ae011633fc443c7d5449c", size = 29100, upload-time = "2025-01-14T19:05:55.319Z" } wheels = [ @@ -4198,10 +4202,10 @@ name = "pyobjc-framework-videotoolbox" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/2d/c031a132b142fcd20846cc1ac3ba92abaa58ec04164fd36ca978d9374f1c/pyobjc_framework_videotoolbox-11.0.tar.gz", hash = "sha256:a54ed8f8bcbdd2bdea2a296dc02a8a7d42f81e2b6ccbf4d1f10cec5e7a09bec0", size = 81157, upload-time = "2025-01-14T19:05:56.135Z" } wheels = [ @@ -4214,8 +4218,8 @@ name = "pyobjc-framework-virtualization" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/8d/e57e1f2c5ac950dc3da6c977effde4a55b8b70424b1bdb97b5530559f5bc/pyobjc_framework_virtualization-11.0.tar.gz", hash = "sha256:03e1c1fa20950aa7c275e5f11f1257108b6d1c6a7403afb86f4e9d5fae87b73c", size = 78144, upload-time = "2025-01-14T19:05:57.086Z" } wheels = [ @@ -4228,10 +4232,10 @@ name = "pyobjc-framework-vision" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreml" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/53/dc2e0562a177af9306efceb84bc21f5cf7470acaa8f28f64e62bf828b7e1/pyobjc_framework_vision-11.0.tar.gz", hash = "sha256:45342e5253c306dbcd056a68bff04ffbfa00e9ac300a02aabf2e81053b771e39", size = 133175, upload-time = "2025-01-14T19:05:58.013Z" } wheels = [ @@ -4244,8 +4248,8 @@ name = "pyobjc-framework-webkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/4f/02a6270acf225c2a34339677e796002c77506238475059ae6e855358a40c/pyobjc_framework_webkit-11.0.tar.gz", hash = "sha256:fa6bedf9873786b3376a74ce2ea9dcd311f2a80f61e33dcbd931cc956aa29644", size = 767210, upload-time = "2025-01-14T19:05:59.3Z" } wheels = [ @@ -4258,9 +4262,9 @@ name = "pyopencl" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pytools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "pytools" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/88/0ac460d3e2def08b2ad6345db6a13613815f616bbbd60c6f4bdf774f4c41/pyopencl-2025.1.tar.gz", hash = "sha256:0116736d7f7920f87b8db4b66a03f27b1d930d2e37ddd14518407cc22dd24779", size = 422510, upload-time = "2025-01-22T00:16:58.421Z" } wheels = [ @@ -4436,14 +4440,14 @@ wheels = [ [[package]] name = "pytest-timeout" -version = "2.3.1" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/0d/04719abc7a4bdb3a7a1f968f24b0f5253d698c9cc94975330e9d3145befb/pytest-timeout-2.3.1.tar.gz", hash = "sha256:12397729125c6ecbdaca01035b9e5239d4db97352320af155b3f5de1ba5165d9", size = 17697, upload-time = "2024-03-07T21:04:01.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/27/14af9ef8321f5edc7527e47def2a21d8118c6f329a9342cc61387a0c0599/pytest_timeout-2.3.1-py3-none-any.whl", hash = "sha256:68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e", size = 14148, upload-time = "2024-03-07T21:03:58.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] [[package]] @@ -4476,7 +4480,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six", marker = "sys_platform != 'darwin'" }, + { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } wheels = [ @@ -4494,9 +4498,9 @@ name = "pytools" version = "2024.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "siphash24", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, + { name = "siphash24" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/0f/56e109c0307f831b5d598ad73976aaaa84b4d0e98da29a642e797eaa940c/pytools-2024.1.10.tar.gz", hash = "sha256:9af6f4b045212c49be32bb31fe19606c478ee4b09631886d05a32459f4ce0a12", size = 81741, upload-time = "2024-07-17T18:47:38.287Z" } wheels = [ @@ -4582,14 +4586,14 @@ wheels = [ [[package]] name = "pyyaml-env-tag" -version = "0.1" +version = "1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631, upload-time = "2020-11-12T02:38:26.239Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/95/32c8c79d784552ed687c676924381c0dc88b2a0248b50a32f4b5ac0ba03c/pyyaml_env_tag-1.0.tar.gz", hash = "sha256:bc952534a872b583f66f916e2dd83e7a7b9087847f4afca6d9c957c48b258ed2", size = 4462, upload-time = "2025-05-09T18:09:14.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911, upload-time = "2020-11-12T02:38:24.638Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8c/c35fdb193c3717bdb4dea0ea361dbe81997164e01deaa2809cc2d71aa6b6/pyyaml_env_tag-1.0-py3-none-any.whl", hash = "sha256:37f081041b8dca44ed8eb931ce0056f97de17251450f0ed08773dc2bcaf9e683", size = 4681, upload-time = "2025-05-09T18:09:12.611Z" }, ] [[package]] @@ -4671,7 +4675,7 @@ wheels = [ [[package]] name = "rerun-sdk" -version = "0.23.1" +version = "0.23.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -4681,11 +4685,11 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/6e/a125f4fe2de3269f443b7cb65d465ffd37a836a2dac7e4318e21239d78c8/rerun_sdk-0.23.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fe06d21cfcf4d84a9396f421d4779efabec7e9674d232a2c552c8a91d871c375", size = 66094053, upload-time = "2025-04-25T13:15:48.669Z" }, - { url = "https://files.pythonhosted.org/packages/55/f6/b6d13322b05dc77bd9a0127e98155c2b7ee987a236fd4d331eed2e547a90/rerun_sdk-0.23.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:823ae87bfa644e06fb70bada08a83690dd23d9824a013947f80a22c6731bdc0d", size = 62047843, upload-time = "2025-04-25T13:15:54.48Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7f/6a7422cb727e14a65b55b0089988eeea8d0532c429397a863e6ba395554a/rerun_sdk-0.23.1-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:dc5129f8744f71249bf45558c853422c51ef39b6b5eea0ea1f602c6049ce732f", size = 68214509, upload-time = "2025-04-25T13:15:59.339Z" }, - { url = "https://files.pythonhosted.org/packages/4f/86/3aee9eadbfe55188a2c7d739378545b4319772a4d3b165e8d3fc598fa630/rerun_sdk-0.23.1-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:ee0d0e17df0e08be13b77cc74884c5d8ba8edb39b6f5a60dc2429d39033d90f6", size = 71442196, upload-time = "2025-04-25T13:16:04.405Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ba/028bd382e2ae21e6643cec25f423285dbc6b328ce56d55727b4101ef9443/rerun_sdk-0.23.1-cp39-abi3-win_amd64.whl", hash = "sha256:d4273db55b56310b053a2de6bf5927a8692cf65f4d234c6e6928fb24ed8a960d", size = 57583198, upload-time = "2025-04-25T13:16:08.905Z" }, + { url = "https://files.pythonhosted.org/packages/24/98/ee9acc4ac36e977bcacd3d65704127026a02228f4f5efa9cfd4243dd2036/rerun_sdk-0.23.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fb9f0279a8b87f64bc29ed2c4c3cbb602e36b9b373f691ff657968ed6009c5c3", size = 66816731, upload-time = "2025-05-06T15:54:46.177Z" }, + { url = "https://files.pythonhosted.org/packages/3a/84/6164958c9b4dcd9b9dab16e1adbf4cea4339f52ce212c084b57bc0fd1a05/rerun_sdk-0.23.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:02d99f48659abeca86fcbd56498d30ab0c4b34b8a0732b02324c3fe79830cf6f", size = 62697629, upload-time = "2025-05-06T15:54:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/c0/4d/1b9dc66a5827f25f9ce5fe9b719f97f42403d1a2276e765082d224590dbe/rerun_sdk-0.23.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:fdc844eddb6dcc924e48b3cd8dd88ed8b70751301d386175dd2fd7c9594580b3", size = 68935354, upload-time = "2025-05-06T15:54:56.27Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7b/16b9bcd67b3dea1842d556100ce2e0b5c6bd9ad7943aeab2a76bae4b2be5/rerun_sdk-0.23.2-cp39-abi3-manylinux_2_31_x86_64.whl", hash = "sha256:dac599ef5d5563f4ebc90c20f09b77c458bac4df845e178112257f79f3eef579", size = 72209426, upload-time = "2025-05-06T15:55:00.641Z" }, + { url = "https://files.pythonhosted.org/packages/67/54/ecb761555f6fbd4e32c384ba5a8424c761925be54259708093205c916c64/rerun_sdk-0.23.2-cp39-abi3-win_amd64.whl", hash = "sha256:ce3f9adfb04df79f30ca1e6deb997b021af87878672f79dfa3543a4f6a83491f", size = 58180134, upload-time = "2025-05-06T15:55:06.284Z" }, ] [[package]] @@ -4737,27 +4741,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.8" +version = "0.11.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/f6/adcf73711f31c9f5393862b4281c875a462d9f639f4ccdf69dc368311c20/ruff-0.11.8.tar.gz", hash = "sha256:6d742d10626f9004b781f4558154bb226620a7242080e11caeffab1a40e99df8", size = 4086399, upload-time = "2025-05-01T14:53:24.459Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/60/c6aa9062fa518a9f86cb0b85248245cddcd892a125ca00441df77d79ef88/ruff-0.11.8-py3-none-linux_armv6l.whl", hash = "sha256:896a37516c594805e34020c4a7546c8f8a234b679a7716a3f08197f38913e1a3", size = 10272473, upload-time = "2025-05-01T14:52:37.252Z" }, - { url = "https://files.pythonhosted.org/packages/a0/e4/0325e50d106dc87c00695f7bcd5044c6d252ed5120ebf423773e00270f50/ruff-0.11.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab86d22d3d721a40dd3ecbb5e86ab03b2e053bc93c700dc68d1c3346b36ce835", size = 11040862, upload-time = "2025-05-01T14:52:41.022Z" }, - { url = "https://files.pythonhosted.org/packages/e6/27/b87ea1a7be37fef0adbc7fd987abbf90b6607d96aa3fc67e2c5b858e1e53/ruff-0.11.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:258f3585057508d317610e8a412788cf726efeefa2fec4dba4001d9e6f90d46c", size = 10385273, upload-time = "2025-05-01T14:52:43.551Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f7/3346161570d789045ed47a86110183f6ac3af0e94e7fd682772d89f7f1a1/ruff-0.11.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727d01702f7c30baed3fc3a34901a640001a2828c793525043c29f7614994a8c", size = 10578330, upload-time = "2025-05-01T14:52:45.48Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c3/327fb950b4763c7b3784f91d3038ef10c13b2d42322d4ade5ce13a2f9edb/ruff-0.11.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3dca977cc4fc8f66e89900fa415ffe4dbc2e969da9d7a54bfca81a128c5ac219", size = 10122223, upload-time = "2025-05-01T14:52:47.675Z" }, - { url = "https://files.pythonhosted.org/packages/de/c7/ba686bce9adfeb6c61cb1bbadc17d58110fe1d602f199d79d4c880170f19/ruff-0.11.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c657fa987d60b104d2be8b052d66da0a2a88f9bd1d66b2254333e84ea2720c7f", size = 11697353, upload-time = "2025-05-01T14:52:50.264Z" }, - { url = "https://files.pythonhosted.org/packages/53/8e/a4fb4a1ddde3c59e73996bb3ac51844ff93384d533629434b1def7a336b0/ruff-0.11.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f2e74b021d0de5eceb8bd32919f6ff8a9b40ee62ed97becd44993ae5b9949474", size = 12375936, upload-time = "2025-05-01T14:52:52.394Z" }, - { url = "https://files.pythonhosted.org/packages/ad/a1/9529cb1e2936e2479a51aeb011307e7229225df9ac64ae064d91ead54571/ruff-0.11.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9b5ef39820abc0f2c62111f7045009e46b275f5b99d5e59dda113c39b7f4f38", size = 11850083, upload-time = "2025-05-01T14:52:55.424Z" }, - { url = "https://files.pythonhosted.org/packages/3e/94/8f7eac4c612673ae15a4ad2bc0ee62e03c68a2d4f458daae3de0e47c67ba/ruff-0.11.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1dba3135ca503727aa4648152c0fa67c3b1385d3dc81c75cd8a229c4b2a1458", size = 14005834, upload-time = "2025-05-01T14:52:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/1e/7c/6f63b46b2be870cbf3f54c9c4154d13fac4b8827f22fa05ac835c10835b2/ruff-0.11.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f024d32e62faad0f76b2d6afd141b8c171515e4fb91ce9fd6464335c81244e5", size = 11503713, upload-time = "2025-05-01T14:53:01.244Z" }, - { url = "https://files.pythonhosted.org/packages/3a/91/57de411b544b5fe072779678986a021d87c3ee5b89551f2ca41200c5d643/ruff-0.11.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d365618d3ad747432e1ae50d61775b78c055fee5936d77fb4d92c6f559741948", size = 10457182, upload-time = "2025-05-01T14:53:03.726Z" }, - { url = "https://files.pythonhosted.org/packages/01/49/cfe73e0ce5ecdd3e6f1137bf1f1be03dcc819d1bfe5cff33deb40c5926db/ruff-0.11.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4d9aaa91035bdf612c8ee7266153bcf16005c7c7e2f5878406911c92a31633cb", size = 10101027, upload-time = "2025-05-01T14:53:06.555Z" }, - { url = "https://files.pythonhosted.org/packages/56/21/a5cfe47c62b3531675795f38a0ef1c52ff8de62eaddf370d46634391a3fb/ruff-0.11.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0eba551324733efc76116d9f3a0d52946bc2751f0cd30661564117d6fd60897c", size = 11111298, upload-time = "2025-05-01T14:53:08.825Z" }, - { url = "https://files.pythonhosted.org/packages/36/98/f76225f87e88f7cb669ae92c062b11c0a1e91f32705f829bd426f8e48b7b/ruff-0.11.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:161eb4cff5cfefdb6c9b8b3671d09f7def2f960cee33481dd898caf2bcd02304", size = 11566884, upload-time = "2025-05-01T14:53:11.626Z" }, - { url = "https://files.pythonhosted.org/packages/de/7e/fff70b02e57852fda17bd43f99dda37b9bcf3e1af3d97c5834ff48d04715/ruff-0.11.8-py3-none-win32.whl", hash = "sha256:5b18caa297a786465cc511d7f8be19226acf9c0a1127e06e736cd4e1878c3ea2", size = 10451102, upload-time = "2025-05-01T14:53:14.303Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a9/eaa571eb70648c9bde3120a1d5892597de57766e376b831b06e7c1e43945/ruff-0.11.8-py3-none-win_amd64.whl", hash = "sha256:6e70d11043bef637c5617297bdedec9632af15d53ac1e1ba29c448da9341b0c4", size = 11597410, upload-time = "2025-05-01T14:53:16.571Z" }, - { url = "https://files.pythonhosted.org/packages/cd/be/f6b790d6ae98f1f32c645f8540d5c96248b72343b0a56fab3a07f2941897/ruff-0.11.8-py3-none-win_arm64.whl", hash = "sha256:304432e4c4a792e3da85b7699feb3426a0908ab98bf29df22a31b0cdd098fac2", size = 10713129, upload-time = "2025-05-01T14:53:22.27Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, + { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, + { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, + { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, + { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, + { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, + { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, ] [[package]] @@ -4771,15 +4775,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.27.0" +version = "2.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/b6/a92ae6fa6d7e6e536bc586776b1669b84fb724dfe21b8ff08297f2d7c969/sentry_sdk-2.27.0.tar.gz", hash = "sha256:90f4f883f9eff294aff59af3d58c2d1b64e3927b28d5ada2b9b41f5aeda47daf", size = 323556, upload-time = "2025-04-24T10:09:37.927Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/bb/6a41b2e0e9121bed4d2ec68d50568ab95c49f4744156a9bbb789c866c66d/sentry_sdk-2.28.0.tar.gz", hash = "sha256:14d2b73bc93afaf2a9412490329099e6217761cbab13b6ee8bc0e82927e1504e", size = 325052, upload-time = "2025-05-12T07:53:12.785Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/8b/fb496a45854e37930b57564a20fb8e90dd0f8b6add0491527c00f2163b00/sentry_sdk-2.27.0-py2.py3-none-any.whl", hash = "sha256:c58935bfff8af6a0856d37e8adebdbc7b3281c2b632ec823ef03cd108d216ff0", size = 340786, upload-time = "2025-04-24T10:09:35.897Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4e/b1575833094c088dfdef63fbca794518860fcbc8002aadf51ebe8b6a387f/sentry_sdk-2.28.0-py2.py3-none-any.whl", hash = "sha256:51496e6cb3cb625b99c8e08907c67a9112360259b0ef08470e532c3ab184a232", size = 341693, upload-time = "2025-05-12T07:53:10.882Z" }, ] [[package]] @@ -4816,11 +4820,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.3.1" +version = "80.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/dc/3976b322de9d2e87ed0007cf04cc7553969b6c7b3f48a565d0333748fbcd/setuptools-80.3.1.tar.gz", hash = "sha256:31e2c58dbb67c99c289f51c16d899afedae292b978f8051efaf6262d8212f927", size = 1315082, upload-time = "2025-05-04T18:47:04.397Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008, upload-time = "2025-05-09T20:42:27.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/7e/5d8af3317ddbf9519b687bd1c39d8737fde07d97f54df65553faca5cffb1/setuptools-80.3.1-py3-none-any.whl", hash = "sha256:ea8e00d7992054c4c592aeb892f6ad51fe1b4d90cc6947cc45c45717c40ec537", size = 1201172, upload-time = "2025-05-04T18:47:02.575Z" }, + { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" }, ] [[package]] @@ -4828,7 +4832,7 @@ name = "shapely" version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617, upload-time = "2025-04-03T09:15:05.725Z" } wheels = [ @@ -4914,9 +4918,9 @@ wheels = [ [[package]] name = "spidev" -version = "3.6" +version = "3.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/d9/401c0a7be089e02826cf2c201f489876b601f15be100fe391ef9c2faed83/spidev-3.6.tar.gz", hash = "sha256:14dbc37594a4aaef85403ab617985d3c3ef464d62bc9b769ef552db53701115b", size = 11917, upload-time = "2022-11-03T20:39:08.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/99/dd50af8200e224ce9412ad01cdbeeb5b39b2d61acd72138f2b92c4a6d619/spidev-3.7.tar.gz", hash = "sha256:ce628a5ff489f45132679879bff5f455a66abf9751af01843850155b06ae92f0", size = 11616, upload-time = "2025-05-06T14:23:30.783Z" } [[package]] name = "sympy" @@ -5086,7 +5090,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From b5545a6736f7f2e1631afdb14e2b951c81cdf1be Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 May 2025 05:00:18 +0800 Subject: [PATCH 024/142] system/ui: confirm dialog always opens in fullscreen mode (#35192) confirm dialog always opens in fullscreen mode --- system/ui/widgets/confirm_dialog.py | 16 +++++----------- system/ui/widgets/network.py | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index e5ca002ecf..7a25682e27 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,4 +1,5 @@ import pyray as rl +from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.label import gui_text_box @@ -11,10 +12,9 @@ TEXT_AREA_HEIGHT_REDUCTION = 200 BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) -def confirm_dialog(rect: rl.Rectangle, message: str, confirm_text: str, cancel_text: str = "Cancel") -> int: - # Calculate dialog position and size, centered within the parent rectangle - dialog_x = rect.x + (rect.width - DIALOG_WIDTH) / 2 - dialog_y = rect.y + (rect.height - DIALOG_HEIGHT) / 2 +def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") -> int: + dialog_x = (gui_app.width - DIALOG_WIDTH) / 2 + dialog_y = (gui_app.height - DIALOG_HEIGHT) / 2 dialog_rect = rl.Rectangle(dialog_x, dialog_y, DIALOG_WIDTH, DIALOG_HEIGHT) # Calculate button positions at the bottom of the dialog @@ -27,13 +27,7 @@ def confirm_dialog(rect: rl.Rectangle, message: str, confirm_text: str, cancel_t yes_button = rl.Rectangle(yes_button_x, button_y, button_width, BUTTON_HEIGHT) # Draw the dialog background - rl.draw_rectangle( - int(dialog_rect.x), - int(dialog_rect.y), - int(dialog_rect.width), - int(dialog_rect.height), - BACKGROUND_COLOR, - ) + rl.draw_rectangle_rec(dialog_rect, BACKGROUND_COLOR) # Draw the message in the dialog, centered text_rect = rl.Rectangle(dialog_rect.x, dialog_rect.y, dialog_rect.width, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 06448483aa..d94a3facb1 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -75,7 +75,7 @@ class WifiManagerUI: self.state = StateIdle() case StateShowForgetConfirm(network): - result = confirm_dialog(rect, f'Forget Wi-Fi Network "{network.ssid}"?', "Forget") + result = confirm_dialog(f'Forget Wi-Fi Network "{network.ssid}"?', "Forget") if result == 1: self.forget_network(network) elif result == 0: From 2e5014d793f347254efe21c47532db3e6194dae6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 12 May 2025 17:23:58 -0700 Subject: [PATCH 025/142] Tesla: check counters and checksums in car interface (#35195) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index b922517802..e484515ec7 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b9225178021be2b606dab89bf941440562f4cc6c +Subproject commit e484515ec7e329064b923869c1d2f15cf8c443a6 From 51ab7ec8f572dc5490c9d10dd6889d52cc1f427b Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 12 May 2025 18:56:13 -0700 Subject: [PATCH 026/142] feat(clip): ffmpeg improvements to reduce frame skips (#35196) * test * no benefit with two pass * rm * typo, testing * re-add --- tools/clip/run.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 12ac879baa..34efcdb5a8 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -177,9 +177,25 @@ def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, rou overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile=Inter.tff:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") ffmpeg_cmd = [ - 'ffmpeg', '-y', '-video_size', RESOLUTION, '-framerate', str(FRAMERATE), '-f', 'x11grab', '-draw_mouse', '0', - '-i', display, '-c:v', 'libx264', '-maxrate', f'{bit_rate_kbps}k', '-bufsize', f'{bit_rate_kbps*2}k', '-crf', '23', - '-filter:v', ','.join(overlays), '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', '-f', 'mp4', '-t', str(duration), out + 'ffmpeg', '-y', + '-video_size', RESOLUTION, + '-framerate', str(FRAMERATE), + '-f', 'x11grab', + '-rtbufsize', '100M', + '-draw_mouse', '0', + '-i', display, + '-c:v', 'libx264', + '-maxrate', f'{bit_rate_kbps}k', + '-bufsize', f'{bit_rate_kbps*2}k', + '-crf', '23', + '-filter:v', ','.join(overlays), + '-preset', 'ultrafast', + '-tune', 'zerolatency', + '-pix_fmt', 'yuv420p', + '-movflags', '+faststart', + '-f', 'mp4', + '-t', str(duration), + out, ] replay_cmd = [REPLAY, '-c', '1', '-s', str(begin_at), '--prefix', prefix] From 6b4436b9804e4149043bae81dbadd6f57680397c Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 12 May 2025 19:49:37 -0700 Subject: [PATCH 027/142] feat(clip): font from `selfdrive/assets` (#35194) * feat(clip): find font dynamically * check font path as part of validating environment * from testing * use font from assets * no mas --- tools/clip/run.py | 7 ++++--- tools/install_ubuntu_dependencies.sh | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 34efcdb5a8..5fa3c0ae69 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -28,6 +28,7 @@ RESOLUTION = '2160x1080' SECONDS_TO_WARM = 2 PROC_WAIT_SECONDS = 30 +OPENPILOT_FONT = str(Path(BASEDIR, 'selfdrive/assets/fonts/Inter-Regular.ttf').resolve()) REPLAY = str(Path(BASEDIR, 'tools/replay/replay').resolve()) UI = str(Path(BASEDIR, 'selfdrive/ui/ui').resolve()) @@ -169,12 +170,12 @@ def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, rou meta_text = get_meta_text(route) overlays = [ # metadata overlay - f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile=Inter.tff:fontcolor=white:fontsize=18:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", + f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=15:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", # route time overlay - f"drawtext=text='%{{eif\\:floor(({start}+t)/60)\\:d\\:2}}\\:%{{eif\\:mod({start}+t\\,60)\\:d\\:2}}':fontfile=Inter.tff:fontcolor=white:fontsize=24:{box_style}:x=w-text_w-38:y=38" + f"drawtext=text='%{{eif\\:floor(({start}+t)/60)\\:d\\:2}}\\:%{{eif\\:mod({start}+t\\,60)\\:d\\:2}}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=24:{box_style}:x=w-text_w-38:y=38" ] if title: - overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile=Inter.tff:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") + overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") ffmpeg_cmd = [ 'ffmpeg', '-y', diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 3d6fd2e7a9..f33569704a 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -33,7 +33,6 @@ function install_ubuntu_common_requirements() { git \ git-lfs \ ffmpeg \ - fonts-inter \ libavformat-dev \ libavcodec-dev \ libavdevice-dev \ From 3f724400c58fb50d8c657ce0177d275ecaae788f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 12 May 2025 21:15:43 -0700 Subject: [PATCH 028/142] Tesla: check checksums in safety (#35198) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index e484515ec7..95ee4edd17 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e484515ec7e329064b923869c1d2f15cf8c443a6 +Subproject commit 95ee4edd17ecc6700eac12f2074de6b5478b9477 From 8473774b6581ff2567e9ca3904589aa41432a1dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 12 May 2025 21:18:51 -0700 Subject: [PATCH 029/142] process replay: add Tesla route (#35199) * add tesla * update refs * run tesla on more procs * update refs --- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/process_replay/test_processes.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 1ae4c4a1a8..f5cd05b16e 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -7bf4ae5b92a3ad1f073f675e24e28babad0f2aa0 +b31b7c5c29e6d30ccee2fa5105af778810fcd02e \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index d479369693..a82eab27dd 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -35,6 +35,7 @@ source_segments = [ ("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.MAZDA_CX9_2021 ("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.FORD_BRONCO_SPORT_MK1 ("RIVIAN", "bc095dc92e101734|000000db--ee9fe46e57--1"), # RIVIAN.RIVIAN_R1_GEN1 + ("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"), # TESLA.TESLA_MODEL_Y # Enable when port is tested and dashcamOnly is no longer set #("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.VOLKSWAGEN_PASSAT_NMS @@ -58,6 +59,7 @@ segments = [ ("MAZDA", "regenACF84CCF482|2024-08-30--03-21-55--0"), ("FORD", "regen755D8CB1E1F|2025-04-08--23-13-43--0"), ("RIVIAN", "regen5FCAC896BBE|2025-04-08--23-13-35--0"), + ("TESLA", "2c912ca5de3b1ee9|0000025d--6eb6bcbca4--4"), ] # dashcamOnly makes don't need to be tested until a full port is done @@ -195,7 +197,7 @@ if __name__ == "__main__": continue # to speed things up, we only test all segments on card - if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD', 'RIVIAN'): + if cfg.proc_name != 'card' and car_brand not in ('HYUNDAI', 'TOYOTA', 'HONDA', 'SUBARU', 'FORD', 'RIVIAN', 'TESLA'): continue cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst") From 15a567ffee778c749433325747a60d16dd6f2dd2 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Tue, 13 May 2025 13:03:31 +0200 Subject: [PATCH 030/142] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index b4773f96b3..7eb5dba3dc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b4773f96b38a56089b28bf70b8073a9ddce6d847 +Subproject commit 7eb5dba3dc9960373128244c10fac99bba91f630 From 896dabb1905213bb8192de5c0d9045ecc860380c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 May 2025 21:31:50 +0800 Subject: [PATCH 031/142] system/ui: add right-side text eliding to gui_label (#35201) add optional right-side text eliding to gui_label --- system/ui/lib/label.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/system/ui/lib/label.py b/system/ui/lib/label.py index ccfd89a2ec..5244c6baf2 100644 --- a/system/ui/lib/label.py +++ b/system/ui/lib/label.py @@ -10,13 +10,27 @@ def gui_label( color: rl.Color = DEFAULT_TEXT_COLOR, font_weight: FontWeight = FontWeight.NORMAL, alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE + alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + elide_right: bool = True ): - # Set font based on the provided weight font = gui_app.font(font_weight) - - # Measure text size text_size = rl.measure_text_ex(font, text, font_size, 0) + display_text = text + + # Elide text to fit within the rectangle + if elide_right and text_size.x > rect.width: + ellipsis = "..." + left, right = 0, len(text) + while left < right: + mid = (left + right) // 2 + candidate = text[:mid] + ellipsis + candidate_size = rl.measure_text_ex(font, candidate, font_size, 0) + if candidate_size.x <= rect.width: + left = mid + 1 + else: + right = mid + display_text = text[: left - 1] + ellipsis if left > 0 else ellipsis + text_size = rl.measure_text_ex(font, display_text, font_size, 0) # Calculate horizontal position based on alignment text_x = rect.x + { @@ -33,7 +47,7 @@ def gui_label( }.get(alignment_vertical, 0) # Draw the text in the specified rectangle - rl.draw_text_ex(font, text, rl.Vector2(text_x, text_y), font_size, 0, color) + rl.draw_text_ex(font, display_text, rl.Vector2(text_x, text_y), font_size, 0, color) def gui_text_box( From 73ff9dc944f05941729fa979db32ddacd573e5df Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 13 May 2025 21:34:17 +0800 Subject: [PATCH 032/142] system/ui: add require_full_screen to WifiManagerUI (#35191) add require_full_screen to WifiManagerUI --- system/ui/updater.py | 3 ++- system/ui/widgets/network.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/system/ui/updater.py b/system/ui/updater.py index 42acaf5675..3ee02ce97c 100755 --- a/system/ui/updater.py +++ b/system/ui/updater.py @@ -110,7 +110,8 @@ class Updater: # Draw the Wi-Fi manager UI wifi_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, gui_app.height - MARGIN * 2 - BUTTON_HEIGHT - 20) self.wifi_manager_ui.render(wifi_rect) - + if self.wifi_manager_ui.require_full_screen: + return back_button_rect = rl.Rectangle(MARGIN, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT) if gui_button(back_button_rect, "Back"): diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index d94a3facb1..d34ad44f55 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -84,6 +84,11 @@ class WifiManagerUI: case _: self._draw_network_list(rect) + @property + def require_full_screen(self) -> bool: + """Check if the WiFi UI requires exclusive full-screen rendering.""" + return isinstance(self.state, (StateNeedsAuth, StateShowForgetConfirm)) + def _draw_network_list(self, rect: rl.Rectangle): content_rect = rl.Rectangle(rect.x, rect.y, rect.width, len(self._networks) * ITEM_HEIGHT) offset = self.scroll_panel.handle_scroll(rect, content_rect) From 480cc57da38358a54de2718c5f28d0d3722c4b39 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Tue, 13 May 2025 10:32:33 -0600 Subject: [PATCH 033/142] Pass args to op auth (#35202) --- tools/op.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/op.sh b/tools/op.sh index 93e774a2ac..a7ed964812 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -254,7 +254,7 @@ function op_setup() { function op_auth() { op_before_cmd - op_run_command tools/lib/auth.py + op_run_command tools/lib/auth.py "$@" } function op_activate_venv() { From 3d696c0f90df5985ac0ff88df2afef8c39512e7f Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 13 May 2025 13:34:54 -0700 Subject: [PATCH 034/142] jenkins: remove logs during setup --- selfdrive/test/setup_device_ci.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 69555b19f7..d717698514 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -18,6 +18,9 @@ if [ -z "$TEST_DIR" ]; then exit 1 fi +# prevent storage from filling up +rm -rf /data/media/0/realdata/* + rm -rf /data/safe_staging/ || true if [ -d /data/safe_staging/ ]; then sudo umount /data/safe_staging/merged/ || true From 313ede3d1ed9b646ff5e94a83aaafe91ba5b6029 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 May 2025 14:48:40 -0700 Subject: [PATCH 035/142] Update NDOG live (#35205) * no alt exp * live * fix * update res --- opendbc_repo | 2 +- selfdrive/car/card.py | 8 -------- selfdrive/selfdrived/selfdrived.py | 4 ++-- selfdrive/test/process_replay/process_replay.py | 7 ------- selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 4 insertions(+), 19 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 95ee4edd17..a633eeb65c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 95ee4edd17ecc6700eac12f2074de6b5478b9477 +Subproject commit a633eeb65c0807746cd9cf5cb1bb1dc227779f3f diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 378543164c..502daaa015 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -17,7 +17,6 @@ from opendbc.car.carlog import carlog from opendbc.car.fw_versions import ObdCallback from opendbc.car.car_helpers import get_car, interfaces from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase -from opendbc.safety import ALTERNATIVE_EXPERIENCE from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp from openpilot.selfdrive.car.cruise import VCruiseHelper from openpilot.selfdrive.car.car_specific import MockCarState @@ -108,16 +107,9 @@ class Car: self.CI, self.CP = CI, CI.CP self.RI = RI - # set alternative experiences from parameters - disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.CP.alternativeExperience = 0 - if not disengage_on_accelerator: - self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") - controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly - self.CP.passive = not controller_available or self.CP.dashcamOnly if self.CP.passive: safety_config = structs.CarParams.SafetyConfig() diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index c888551777..98c03acf1e 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -7,7 +7,6 @@ import cereal.messaging as messaging from cereal import car, log from msgq.visionipc import VisionIpcClient, VisionStreamType -from opendbc.safety import ALTERNATIVE_EXPERIENCE from openpilot.common.params import Params @@ -55,7 +54,6 @@ class SelfdriveD: self.CP = CP self.car_events = CarSpecificEvents(self.CP) - self.disengage_on_accelerator = not (self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS) # Setup sockets self.pm = messaging.PubMaster(['selfdriveState', 'onroadEvents']) @@ -85,6 +83,7 @@ class SelfdriveD: # read params self.is_metric = self.params.get_bool("IsMetric") self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled") + self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") car_recognized = self.CP.brand != 'mock' @@ -486,6 +485,7 @@ class SelfdriveD: def params_thread(self, evt): while not evt.is_set(): self.is_metric = self.params.get_bool("IsMetric") + self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl self.personality = self.read_personality_param() time.sleep(0.1) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 3183db0c3a..8de2e60055 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -17,7 +17,6 @@ from cereal import car from cereal.services import SERVICE_LIST from msgq.visionipc import VisionIpcServer, get_endpoint_name as vipc_get_endpoint_name from opendbc.car.car_helpers import get_car, interfaces -from opendbc.safety import ALTERNATIVE_EXPERIENCE from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout @@ -365,9 +364,6 @@ def get_car_params_callback(rc, pm, msgs, fingerprint): CP = get_car(*can_callbacks, lambda obd: None, Params().get_bool("AlphaLongitudinalEnabled"), cached_params=cached_params).CP - if not params.get_bool("DisengageOnAccelerator"): - CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - params.put("CarParams", CP.to_bytes()) @@ -769,9 +765,6 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non params_dict["IsRhdDetected"] = is_rhd if CP is not None: - if CP.alternativeExperience == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS: - params_dict["DisengageOnAccelerator"] = False - if fingerprint is None: if CP.fingerprintSource == "fw": params_dict["CarParamsCache"] = CP.as_builder().to_bytes() diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f5cd05b16e..ab602d4726 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b31b7c5c29e6d30ccee2fa5105af778810fcd02e \ No newline at end of file +61bfff810e45b7a1577cfbb572a7b3c233f8551b \ No newline at end of file From 67486ff92d9f78bcd14865a8bb81de0b94ded075 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 May 2025 16:59:35 -0700 Subject: [PATCH 036/142] bump tinygrad (#35208) * bump tinygrad * fix * why is mac different? * fix sim * relax that --- .gitmodules | 2 +- selfdrive/modeld/SConscript | 4 ++-- selfdrive/modeld/modeld.py | 5 +++-- selfdrive/test/test_onroad.py | 2 +- tinygrad_repo | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index 54c7393986..ad6530de9a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -15,4 +15,4 @@ url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/commaai/tinygrad.git + url = https://github.com/tinygrad/tinygrad.git diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index cecebfa18b..0704dd4704 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -43,9 +43,9 @@ pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abs if arch == 'larch64': device_string = 'QCOM=1' elif arch == 'Darwin': - device_string = 'CLANG=1 IMAGE=0' + device_string = 'CLANG=1 IMAGE=0 JIT=2' else: - device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0' + device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2' for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 5e1acdea0e..814bc7a999 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 import os from openpilot.system.hardware import TICI -from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes if TICI: from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address os.environ['QCOM'] = '1' else: os.environ['LLVM'] = '1' + os.environ['JIT'] = '2' +from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 4cd952219c..d0c725cacb 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -398,7 +398,7 @@ class TestOnroad: ("modelV2", 0.06, 0.040), # can miss cycles here and there, just important the avg frequency is 20Hz - ("driverStateV2", 0.2, 0.05), + ("driverStateV2", 0.3, 0.05), ] for (s, instant_max, avg_max) in cfgs: ts = [getattr(m, s).modelExecutionTime for m in self.msgs[s]] diff --git a/tinygrad_repo b/tinygrad_repo index 0e34f9082e..ddff9857b8 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 0e34f9082e9730b5df9c055b094a43e4565e413b +Subproject commit ddff9857b8527c16e5917eeec2dc55da69fa74d2 From 478015e7888424d255188e2a608e8d78ec95e740 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 13 May 2025 17:04:16 -0700 Subject: [PATCH 037/142] typo --- tools/scripts/setup_ssh_keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/scripts/setup_ssh_keys.py b/tools/scripts/setup_ssh_keys.py index 699765eee1..45dc0aa977 100755 --- a/tools/scripts/setup_ssh_keys.py +++ b/tools/scripts/setup_ssh_keys.py @@ -18,6 +18,6 @@ if __name__ == "__main__": params.put_bool("SshEnabled", True) params.put("GithubSshKeys", keys.text) params.put("GithubUsername", username) - print("Setup ssh keys successfully") + print("Set up ssh keys successfully") else: print("Error getting public keys from github") From d0bf2be6f04e5035a21f1b5fa1a7f4d2f0217f8b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 May 2025 17:12:32 -0700 Subject: [PATCH 038/142] External GPU support for big models (#35172) * usb gpu * cleanup --------- Co-authored-by: Comma Device --- selfdrive/modeld/SConscript | 19 ++++++++++--------- selfdrive/modeld/modeld.py | 12 +++++++++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 0704dd4704..28d60b9e00 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,3 +1,4 @@ +import os import glob Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc', 'transformations') @@ -13,7 +14,6 @@ common_src = [ "transforms/transform.cc", ] - # OpenCL is a framework on Mac if arch == "Darwin": frameworks += ['OpenCL'] @@ -40,15 +40,16 @@ for model_name in ['driving_vision', 'driving_policy']: # Compile tinygrad model pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' -if arch == 'larch64': - device_string = 'QCOM=1' -elif arch == 'Darwin': - device_string = 'CLANG=1 IMAGE=0 JIT=2' -else: - device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2' - for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: + if "USBGPU" in os.environ and not model_name.startswith("dmon"): + device_string = "AMD=1 AMD_LLVM=1 NOLOCALS=0 IMAGE=0" + elif arch == 'larch64': + device_string = 'QCOM=1' + elif arch == 'Darwin': + device_string = 'CLANG=1 IMAGE=0 JIT=2' + else: + device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2' + fn = File(f"models/{model_name}").abspath cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd) - diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 814bc7a999..cdb16a6f63 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,7 +1,10 @@ #!/usr/bin/env python3 import os from openpilot.system.hardware import TICI -if TICI: +USBGPU = "USBGPU" in os.environ +if USBGPU: + os.environ['AMD'] = '1' +elif TICI: from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address os.environ['QCOM'] = '1' else: @@ -147,7 +150,7 @@ class ModelState: imgs_cl = {'input_imgs': self.frames['input_imgs'].prepare(buf, transform.flatten()), 'big_input_imgs': self.frames['big_input_imgs'].prepare(wbuf, transform_wide.flatten())} - if TICI: + if TICI and not USBGPU: # The imgs tensors are backed by opencl memory, only need init once for key in imgs_cl: if key not in self.vision_inputs: @@ -188,7 +191,10 @@ def main(demo=False): sentry.set_tag("daemon", PROCESS_NAME) cloudlog.bind(daemon=PROCESS_NAME) setproctitle(PROCESS_NAME) - config_realtime_process(7, 54) + if not USBGPU: + # USB GPU currently saturates a core so can't do this yet, + # also need to move the aux USB interrupts for good timings + config_realtime_process(7, 54) cloudlog.warning("setting up CL context") cl_context = CLContext() From 9d7fce8dd1e29a4e8e80eee843b05d4ba7411be9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 May 2025 18:48:21 -0700 Subject: [PATCH 039/142] CI: bump tinygrad in cronjob --- .github/workflows/repo-maintenance.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 3bcb143825..e041b07ec8 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -50,7 +50,7 @@ jobs: - name: bump submodules run: | git config --global --add safe.directory '*' - git -c submodule."tinygrad".update=none submodule update --remote + git submodule update --remote git add . - name: update car docs run: | From 77c35f8e5dd9bc952b63a754eb9ed810983094c7 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Tue, 13 May 2025 18:55:58 -0700 Subject: [PATCH 040/142] [bot] Update Python packages (#35214) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 39 +++++++++++++++++++++------------------ 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index a633eeb65c..7faa3a0811 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit a633eeb65c0807746cd9cf5cb1bb1dc227779f3f +Subproject commit 7faa3a0811ddc7bf7ba38f1c8e2d1b6c17342a30 diff --git a/panda b/panda index 7eb5dba3dc..6adac802b5 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 7eb5dba3dc9960373128244c10fac99bba91f630 +Subproject commit 6adac802b5dce739e35b734b81b8d20fca4bf700 diff --git a/tinygrad_repo b/tinygrad_repo index ddff9857b8..f726f79a9e 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit ddff9857b8527c16e5917eeec2dc55da69fa74d2 +Subproject commit f726f79a9e1b3ab6dafb3dc012feb7901afdf721 diff --git a/uv.lock b/uv.lock index 60e0e521c5..5eafe7b636 100644 --- a/uv.lock +++ b/uv.lock @@ -163,7 +163,7 @@ wheels = [ [[package]] name = "azure-identity" -version = "1.22.0" +version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -172,9 +172,9 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/8e/1b5916f5e1696bf05b009cf7d41383cea54aa8536d4a4f6f88cca15eb6a4/azure_identity-1.22.0.tar.gz", hash = "sha256:c8f5ef23e5295c2fa300c984dd9f5e1fe43503fc25c121c37ff6a15e39b800b9", size = 263346, upload-time = "2025-05-06T20:22:24.13Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/52/458c1be17a5d3796570ae2ed3c6b7b55b134b22d5ef8132b4f97046a9051/azure_identity-1.23.0.tar.gz", hash = "sha256:d9cdcad39adb49d4bb2953a217f62aec1f65bbb3c63c9076da2be2a47e53dde4", size = 265280, upload-time = "2025-05-14T00:18:30.408Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/1a/6f13d7f95f68f37303c0e00e011d498e4524e70d354b2e11ef5ae89e0ce0/azure_identity-1.22.0-py3-none-any.whl", hash = "sha256:26d6c63f2ca453c77c3e74be8613941ad074e05d0c8be135247573752c249ad8", size = 185524, upload-time = "2025-05-06T20:22:25.991Z" }, + { url = "https://files.pythonhosted.org/packages/07/16/a51d47780f41e4b87bb2d454df6aea90a44a346e918ac189d3700f3d728d/azure_identity-1.23.0-py3-none-any.whl", hash = "sha256:dbbeb64b8e5eaa81c44c565f264b519ff2de7ff0e02271c49f3cb492762a50b0", size = 186097, upload-time = "2025-05-14T00:18:32.734Z" }, ] [[package]] @@ -1217,24 +1217,27 @@ wheels = [ [[package]] name = "onnx" -version = "1.17.0" +version = "1.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "protobuf" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/54/0e385c26bf230d223810a9c7d06628d954008a5e5e4b73ee26ef02327282/onnx-1.17.0.tar.gz", hash = "sha256:48ca1a91ff73c1d5e3ea2eef20ae5d0e709bb8a2355ed798ffc2169753013fd3", size = 12165120, upload-time = "2024-10-01T21:48:40.63Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/60/e56e8ec44ed34006e6d4a73c92a04d9eea6163cc12440e35045aec069175/onnx-1.18.0.tar.gz", hash = "sha256:3d8dbf9e996629131ba3aa1afd1d8239b660d1f830c6688dd7e03157cccd6b9c", size = 12563009, upload-time = "2025-05-12T22:03:09.626Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/a9/8d1b1d53aec70df53e0f57e9f9fcf47004276539e29230c3d5f1f50719ba/onnx-1.17.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:d6fc3a03fc0129b8b6ac03f03bc894431ffd77c7d79ec023d0afd667b4d35869", size = 16647991, upload-time = "2024-10-01T21:46:02.491Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e3/cc80110e5996ca61878f7b4c73c7a286cd88918ff35eacb60dc75ab11ef5/onnx-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01a4b63d4e1d8ec3e2f069e7b798b2955810aa434f7361f01bc8ca08d69cce4", size = 15908949, upload-time = "2024-10-01T21:46:05.165Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/91092557ed478e323a2b4471e2081fdf88d1dd52ae988ceaf7db4e4506ff/onnx-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a183c6178be001bf398260e5ac2c927dc43e7746e8638d6c05c20e321f8c949", size = 16048190, upload-time = "2024-10-01T21:46:08.041Z" }, - { url = "https://files.pythonhosted.org/packages/ac/59/9ea23fc22d0bb853133f363e6248e31bcbc6c1c90543a3938c00412ac02a/onnx-1.17.0-cp311-cp311-win32.whl", hash = "sha256:081ec43a8b950171767d99075b6b92553901fa429d4bc5eb3ad66b36ef5dbe3a", size = 14424299, upload-time = "2024-10-01T21:46:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/51/a5/19b0dfcb567b62e7adf1a21b08b23224f0c2d13842aee4d0abc6f07f9cf5/onnx-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:95c03e38671785036bb704c30cd2e150825f6ab4763df3a4f1d249da48525957", size = 14529142, upload-time = "2024-10-01T21:46:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/b4/dd/c416a11a28847fafb0db1bf43381979a0f522eb9107b831058fde012dd56/onnx-1.17.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:0e906e6a83437de05f8139ea7eaf366bf287f44ae5cc44b2850a30e296421f2f", size = 16651271, upload-time = "2024-10-01T21:46:16.084Z" }, - { url = "https://files.pythonhosted.org/packages/f0/6c/f040652277f514ecd81b7251841f96caa5538365af7df07f86c6018cda2b/onnx-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d955ba2939878a520a97614bcf2e79c1df71b29203e8ced478fa78c9a9c63c2", size = 15907522, upload-time = "2024-10-01T21:46:18.574Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7c/67f4952d1b56b3f74a154b97d0dd0630d525923b354db117d04823b8b49b/onnx-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f3fb5cc4e2898ac5312a7dc03a65133dd2abf9a5e520e69afb880a7251ec97a", size = 16046307, upload-time = "2024-10-01T21:46:21.186Z" }, - { url = "https://files.pythonhosted.org/packages/ae/20/6da11042d2ab870dfb4ce4a6b52354d7651b6b4112038b6d2229ab9904c4/onnx-1.17.0-cp312-cp312-win32.whl", hash = "sha256:317870fca3349d19325a4b7d1b5628f6de3811e9710b1e3665c68b073d0e68d7", size = 14424235, upload-time = "2024-10-01T21:46:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/35/55/c4d11bee1fdb0c4bd84b4e3562ff811a19b63266816870ae1f95567aa6e1/onnx-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:659b8232d627a5460d74fd3c96947ae83db6d03f035ac633e20cd69cfa029227", size = 14530453, upload-time = "2024-10-01T21:46:26.981Z" }, + { url = "https://files.pythonhosted.org/packages/ed/3a/a336dac4db1eddba2bf577191e5b7d3e4c26fcee5ec518a5a5b11d13540d/onnx-1.18.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:735e06d8d0cf250dc498f54038831401063c655a8d6e5975b2527a4e7d24be3e", size = 18281831, upload-time = "2025-05-12T22:02:06.429Z" }, + { url = "https://files.pythonhosted.org/packages/02/3a/56475a111120d1e5d11939acbcbb17c92198c8e64a205cd68e00bdfd8a1f/onnx-1.18.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73160799472e1a86083f786fecdf864cf43d55325492a9b5a1cfa64d8a523ecc", size = 17424359, upload-time = "2025-05-12T22:02:09.866Z" }, + { url = "https://files.pythonhosted.org/packages/cf/03/5eb5e9ef446ed9e78c4627faf3c1bc25e0f707116dd00e9811de232a8df5/onnx-1.18.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6acafb3823238bbe8f4340c7ac32fb218689442e074d797bee1c5c9a02fdae75", size = 17586006, upload-time = "2025-05-12T22:02:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4e/70943125729ce453271a6e46bb847b4a612496f64db6cbc6cb1f49f41ce1/onnx-1.18.0-cp311-cp311-win32.whl", hash = "sha256:4c8c4bbda760c654e65eaffddb1a7de71ec02e60092d33f9000521f897c99be9", size = 15734988, upload-time = "2025-05-12T22:02:16.561Z" }, + { url = "https://files.pythonhosted.org/packages/44/b0/435fd764011911e8f599e3361f0f33425b1004662c1ea33a0ad22e43db2d/onnx-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5810194f0f6be2e58c8d6dedc6119510df7a14280dd07ed5f0f0a85bd74816a", size = 15849576, upload-time = "2025-05-12T22:02:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f0/9e31f4b4626d60f1c034f71b411810bc9fafe31f4e7dd3598effd1b50e05/onnx-1.18.0-cp311-cp311-win_arm64.whl", hash = "sha256:aa1b7483fac6cdec26922174fc4433f8f5c2f239b1133c5625063bb3b35957d0", size = 15822961, upload-time = "2025-05-12T22:02:22.735Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fe/16228aca685392a7114625b89aae98b2dc4058a47f0f467a376745efe8d0/onnx-1.18.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:521bac578448667cbb37c50bf05b53c301243ede8233029555239930996a625b", size = 18285770, upload-time = "2025-05-12T22:02:26.116Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/ba50a903a9b5e6f9be0fa50f59eb2fca4a26ee653375408fbc72c3acbf9f/onnx-1.18.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4da451bf1c5ae381f32d430004a89f0405bc57a8471b0bddb6325a5b334aa40", size = 17421291, upload-time = "2025-05-12T22:02:29.645Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/25ec2ba723ac62b99e8fed6d7b59094dadb15e38d4c007331cc9ae3dfa5f/onnx-1.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99afac90b4cdb1471432203c3c1f74e16549c526df27056d39f41a9a47cfb4af", size = 17584084, upload-time = "2025-05-12T22:02:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4d/2c253a36070fb43f340ff1d2c450df6a9ef50b938adcd105693fee43c4ee/onnx-1.18.0-cp312-cp312-win32.whl", hash = "sha256:ee159b41a3ae58d9c7341cf432fc74b96aaf50bd7bb1160029f657b40dc69715", size = 15734892, upload-time = "2025-05-12T22:02:35.527Z" }, + { url = "https://files.pythonhosted.org/packages/e8/92/048ba8fafe6b2b9a268ec2fb80def7e66c0b32ab2cae74de886981f05a27/onnx-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:102c04edc76b16e9dfeda5a64c1fccd7d3d2913b1544750c01d38f1ac3c04e05", size = 15850336, upload-time = "2025-05-12T22:02:38.545Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/bbc4ffedd44165dcc407a51ea4c592802a5391ce3dc94aa5045350f64635/onnx-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:911b37d724a5d97396f3c2ef9ea25361c55cbc9aa18d75b12a52b620b67145af", size = 15823802, upload-time = "2025-05-12T22:02:42.037Z" }, ] [[package]] @@ -4586,14 +4589,14 @@ wheels = [ [[package]] name = "pyyaml-env-tag" -version = "1.0" +version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/95/32c8c79d784552ed687c676924381c0dc88b2a0248b50a32f4b5ac0ba03c/pyyaml_env_tag-1.0.tar.gz", hash = "sha256:bc952534a872b583f66f916e2dd83e7a7b9087847f4afca6d9c957c48b258ed2", size = 4462, upload-time = "2025-05-09T18:09:14.1Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/8c/c35fdb193c3717bdb4dea0ea361dbe81997164e01deaa2809cc2d71aa6b6/pyyaml_env_tag-1.0-py3-none-any.whl", hash = "sha256:37f081041b8dca44ed8eb931ce0056f97de17251450f0ed08773dc2bcaf9e683", size = 4681, upload-time = "2025-05-09T18:09:12.611Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] [[package]] From 98c4a4582bf666c5ae5495301d603dae88a1d62f Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 13 May 2025 18:56:13 -0700 Subject: [PATCH 041/142] chore: reformat clip params for extension (#35212) --- tools/clip/run.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 5fa3c0ae69..9414f8fba7 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -156,7 +156,17 @@ def wait_for_frames(procs: list[Popen]): check_for_failure(proc) -def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, route: Route, out: str, start: int, end: int, target_mb: int, title: str | None): +def clip( + data_dir: str | None, + quality: Literal['low', 'high'], + prefix: str, + route: Route, + out: str, + start: int, + end: int, + target_mb: int, + title: str | None, +): logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB') begin_at = max(start - SECONDS_TO_WARM, 0) @@ -256,7 +266,17 @@ def main(): p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) args = parse_args(p) try: - clip(args.data_dir, args.quality, args.prefix, args.route, args.output, args.start, args.end, args.file_size, args.title) + clip( + data_dir=args.data_dir, + quality=args.quality, + prefix=args.prefix, + route=args.route, + out=args.output, + start=args.start, + end=args.end, + target_mb=args.file_size, + title=args.title, + ) except KeyboardInterrupt as e: logger.exception('interrupted by user', exc_info=e) except Exception as e: From 1a3e3423035112b287a8fd0f73ef4222e4dd58ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Wed, 14 May 2025 04:22:44 +0200 Subject: [PATCH 042/142] ui: reset live parameters and delay when reset calibration pressed (#35211) * Reset params and lag too * Old param too --- selfdrive/ui/qt/offroad/settings.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 7a79413e2d..2057f1223e 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -194,6 +194,9 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { params.remove("CalibrationParams"); params.remove("LiveTorqueParameters"); + params.remove("LiveParameters"); + params.remove("LiveParametersV2"); + params.remove("LiveDelay"); } }); addItem(resetCalibBtn); @@ -326,7 +329,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { if (param.endsWith("Panel")) { QString panelName = param; panelName.chop(5); // Remove "Panel" suffix - + // Find the panel by name for (int i = 0; i < nav_btns->buttons().size(); i++) { if (nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) { @@ -338,7 +341,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { emit expandToggleDescription(param); } } - + panel_widget->setCurrentIndex(index); nav_btns->buttons()[index]->setChecked(true); } From bdffd3408855b167e394199d4550d48aa566c225 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 13 May 2025 19:23:36 -0700 Subject: [PATCH 043/142] feat(clip): support metric units if requested (#35213) --- tools/clip/run.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/clip/run.py b/tools/clip/run.py index 9414f8fba7..d6b75566b1 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -15,6 +15,7 @@ from typing import Literal from cereal.messaging import SubMaster from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.tools.lib.route import Route @@ -166,6 +167,7 @@ def clip( end: int, target_mb: int, title: str | None, + use_metric: bool = False, ): logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB') @@ -225,8 +227,11 @@ def clip( xvfb_proc = start_proc(xvfb_cmd, env) atexit.register(lambda: xvfb_proc.terminate()) + ui_proc = start_proc(ui_cmd, env) atexit.register(lambda: ui_proc.terminate()) + Params().put('IsMetric', '1' if use_metric else '0') + replay_proc = start_proc(replay_cmd, env) atexit.register(lambda: replay_proc.terminate()) procs = [replay_proc, ui_proc, xvfb_proc] @@ -263,6 +268,7 @@ def main(): p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}') p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high') p.add_argument('-s', '--start', help='start clipping at seconds', type=int) + p.add_argument('-m', '--metric', help='use metric units in ui (e.g. kph)', action='store_true') p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) args = parse_args(p) try: @@ -276,6 +282,7 @@ def main(): end=args.end, target_mb=args.file_size, title=args.title, + use_metric=args.metric, ) except KeyboardInterrupt as e: logger.exception('interrupted by user', exc_info=e) From cbec1886e7e17e5ab4d8ab80409c39492ac21917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Wed, 14 May 2025 04:26:28 +0200 Subject: [PATCH 044/142] paramsd: remove saved param if mismatch (#35215) remove LiveParametersV2 --- selfdrive/locationd/paramsd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index a7712ba1d9..ec15f501ae 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -248,6 +248,7 @@ def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: b retrieve_success = True except Exception as e: cloudlog.error(f"Failed to retrieve initial values: {e}") + params.remove("LiveParametersV2") if not replay: # When driving in wet conditions the stiffness can go down, and then be too low on the next drive From f242b1e88a703d7f6f3ab53f4534e7c684d6ebe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Wed, 14 May 2025 05:18:14 +0200 Subject: [PATCH 045/142] lagd: remove saved param if mismatch (#35217) Remove LiveDelay on mismatch --- selfdrive/locationd/lagd.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index f4f46b7469..b4f4e1f663 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -316,9 +316,9 @@ class LateralLagEstimator: return lag, corr -def retrieve_initial_lag(params_reader: Params, CP: car.CarParams): - last_lag_data = params_reader.get("LiveDelay") - last_carparams_data = params_reader.get("CarParamsPrevRoute") +def retrieve_initial_lag(params: Params, CP: car.CarParams): + last_lag_data = params.get("LiveDelay") + last_carparams_data = params.get("CarParamsPrevRoute") if last_lag_data is not None: try: @@ -333,6 +333,7 @@ def retrieve_initial_lag(params_reader: Params, CP: car.CarParams): return lag, valid_blocks except Exception as e: cloudlog.error(f"Failed to retrieve initial lag: {e}") + params.remove("LiveDelay") return None @@ -345,11 +346,11 @@ def main(): pm = messaging.PubMaster(['liveDelay']) sm = messaging.SubMaster(['livePose', 'liveCalibration', 'carState', 'controlsState', 'carControl'], poll='livePose') - params_reader = Params() - CP = messaging.log_from_bytes(params_reader.get("CarParams", block=True), car.CarParams) + params = Params() + CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) lag_learner = LateralLagEstimator(CP, 1. / SERVICE_LIST['livePose'].frequency) - if (initial_lag_params := retrieve_initial_lag(params_reader, CP)) is not None: + if (initial_lag_params := retrieve_initial_lag(params, CP)) is not None: lag, valid_blocks = initial_lag_params lag_learner.reset(lag, valid_blocks) @@ -370,4 +371,4 @@ def main(): pm.send('liveDelay', lag_msg_dat) if sm.frame % 1200 == 0: # cache every 60 seconds - params_reader.put_nonblocking("LiveDelay", lag_msg_dat) + params.put_nonblocking("LiveDelay", lag_msg_dat) From 3fd05d2aceb2fc52c96efce58ae3e7c44d882c4a Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 13 May 2025 21:05:17 -0700 Subject: [PATCH 046/142] more robust modem --- system/hardware/tici/hardware.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 694d42f4d6..5a8e41b51f 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -297,13 +297,11 @@ class Tici(HardwareBase): return None def get_modem_temperatures(self): - if self.get_device_type() == "mici": - return [] timeout = 0.2 # Default timeout is too short try: modem = self.get_modem() temps = modem.Command("AT+QTEMP", math.ceil(timeout), dbus_interface=MM_MODEM, timeout=timeout) - return list(map(int, temps.split(' ')[1].split(','))) + return list(filter(lambda t: t != 255, map(int, temps.split(' ')[1].split(',')))) except Exception: return [] From 997a99926cba018a48a513bf0ae8d834d7749145 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 13 May 2025 21:06:30 -0700 Subject: [PATCH 047/142] ci: overwrite previous comment for model_replay (#35209) * less * test * fix * cleanup --- selfdrive/test/process_replay/model_replay.py | 2 +- tools/lib/github_utils.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index af626e2da9..0578a61588 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -123,7 +123,7 @@ def comment_replay_report(proposed, master, full_logs): diff_plots = create_table("Model Replay Differences", diff_files, link, open_table=True) all_plots = create_table("All Model Replay Plots", files, link) comment = f"ref for commit {commit}: {link}/{log_name}" + diff_plots + all_plots - GITHUB.comment_on_pr(comment, PR_BRANCH) + GITHUB.comment_on_pr(comment, PR_BRANCH, "commaci-public", True) def trim_logs_to_max_frames(logs, max_frames, frs_types, include_all_types): all_msgs = [] diff --git a/tools/lib/github_utils.py b/tools/lib/github_utils.py index 4dc22b9524..46a0dcf3cb 100644 --- a/tools/lib/github_utils.py +++ b/tools/lib/github_utils.py @@ -87,8 +87,8 @@ class GithubUtils: def comment_on_pr(self, comment, pr_branch, commenter="", overwrite=False): pr_number = self.get_pr_number(pr_branch) data = f'{{"body": "{comment}"}}' + github_path = f'issues/{pr_number}/comments' if overwrite: - github_path = f'issues/{pr_number}/comments' r = self.api_call(github_path) comments = [x['id'] for x in r.json() if x['user']['login'] == commenter] if comments: @@ -96,7 +96,6 @@ class GithubUtils: self.api_call(github_path, data=data, method=HTTPMethod.PATCH) return - github_path=f'issues/{pr_number}/comments' self.api_call(github_path, data=data, method=HTTPMethod.POST) # upload files to github and comment them on the pr From 92e27f0ba29eb254e7ab2c955aa2f2ae71044287 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 14 May 2025 14:34:11 +0100 Subject: [PATCH 048/142] fix .editorconfig --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 879e6eebca..d506433ece 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[{*.py, *.pyx, *.pxd}] +[*.{py,pyx,pxd}] charset = utf-8 indent_style = space indent_size = 2 From c57f576436083e61ee134865aca3623146a28e2a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 14 May 2025 21:49:47 +0800 Subject: [PATCH 049/142] system/ui: fix FPS drop issues (#35220) set VSYNC flag only when the env is set --- system/ui/lib/application.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index c359b1e578..b9d4293738 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -12,6 +12,7 @@ FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions +ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1" DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' @@ -52,9 +53,13 @@ class GuiApplication: HARDWARE.set_screen_brightness(65) self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_ALL) - rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT) + + flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT + if ENABLE_VSYNC: + flags |= rl.ConfigFlags.FLAG_VSYNC_HINT + rl.set_config_flags(flags) + rl.init_window(self._width, self._height, title) rl.set_target_fps(fps) From b1bdab0c9084632d930eeef0de6307ef3cb3f458 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 14 May 2025 23:11:38 +0800 Subject: [PATCH 050/142] system/ui: improve texture caching (#35222) improve texture caching --- system/ui/lib/application.py | 50 ++++++++++++++++++++++++++++++------ system/ui/spinner.py | 7 ++--- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index b9d4293738..ba1441170b 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -18,6 +18,7 @@ STRICT_MODE = os.getenv("STRICT_MODE") == '1' DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.Color(200, 200, 200, 255) +ASSETS_DIR = os.path.join(BASEDIR, "selfdrive/assets") FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts") @@ -37,7 +38,7 @@ class GuiApplication: self._fonts: dict[FontWeight, rl.Font] = {} self._width = width self._height = height - self._textures: list[rl.Texture] = [] + self._textures: dict[str, rl.Texture] = {} self._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() self._window_close_requested = False @@ -67,28 +68,61 @@ class GuiApplication: self._set_styles() self._load_fonts() - def load_texture_from_image(self, file_name: str, width: int, height: int, alpha_premultiply = False): + + def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): + cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" + if cache_key in self._textures: + return self._textures[cache_key] + + texture_obj = self._load_texture_from_image(os.path.join(ASSETS_DIR, asset_path), width, height, alpha_premultiply, keep_aspect_ratio) + self._textures[cache_key] = texture_obj + return texture_obj + + def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply = False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" - image = rl.load_image(file_name) + if image_path.endswith('.svg'): + image = self._load_image_from_svg(image_path) + else: + image = rl.load_image(image_path) + if alpha_premultiply: rl.image_alpha_premultiply(image) - rl.image_resize(image, width, height) + + # Resize with aspect ratio preservation if requested + if keep_aspect_ratio: + orig_width = image.width + orig_height = image.height + + scale_width = width / orig_width + scale_height = height / orig_height + + # Calculate new dimensions + scale = min(scale_width, scale_height) + new_width = int(orig_width * scale) + new_height = int(orig_height * scale) + + rl.image_resize(image, new_width, new_height) + else: + rl.image_resize(image, width, height) + texture = rl.load_texture_from_image(image) # Set texture filtering to smooth the result rl.set_texture_filter(texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) rl.unload_image(image) - - self._textures.append(texture) return texture + def _load_image_from_svg(self, svg_path: str): + # TODO: Implement SVG loading + assert(0) + def close(self): if not rl.is_window_ready(): return - for texture in self._textures: + for texture in self._textures.values(): rl.unload_texture(texture) - self._textures = [] + self._textures = {} for font in self._fonts.values(): rl.unload_font(font) diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 119bdba3e7..93bce3887e 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import pyray as rl -import os import threading import time -from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.window import BaseWindow from openpilot.system.ui.text import wrap_text @@ -26,9 +24,8 @@ def clamp(value, min_value, max_value): class SpinnerRenderer: def __init__(self): - self._comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE) - self._spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE, - alpha_premultiply=True) + self._comma_texture = gui_app.texture("img_spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE) + self._spinner_texture = gui_app.texture("img_spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True) self._rotation = 0.0 self._progress: int | None = None self._wrapped_lines: list[str] = [] From 665d2c3bec3bd4da393e788ac46e5f6719608d7b Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 14 May 2025 08:14:44 -0700 Subject: [PATCH 051/142] feat(clip): hydrate route's CarParams before starting UI (#35218) * feat: hydrate CarParams before starting UI * only get first segment, faster --- tools/clip/run.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index d6b75566b1..87a5d79d2e 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -18,6 +18,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader, ReadMode, comma_api_source DEFAULT_OUTPUT = 'output.mp4' DEMO_START = 90 @@ -114,6 +115,20 @@ def parse_args(parser: ArgumentParser): return args +def populate_car_params(route: Route): + segment = route.name.canonical_name + '/0' # only get first segment qlog + lr = LogReader(segment, default_mode=ReadMode.QLOG, source=comma_api_source) + init_data = lr.first('initData') + assert init_data is not None + + params = Params() + entries = init_data.params.entries + for cp in entries: + key, value = cp.key, cp.value + params.put(key, value) + logger.info(f'persisted {len(entries)} CarParam(s)') + + def start_proc(args: list[str], env: dict[str, str]): return Popen(args, env=env, stdout=PIPE, stderr=PIPE) @@ -167,7 +182,6 @@ def clip( end: int, target_mb: int, title: str | None, - use_metric: bool = False, ): logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB') @@ -222,16 +236,15 @@ def clip( xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}'] with OpenpilotPrefix(prefix, shared_download_cache=True): + populate_car_params(route) + env = os.environ.copy() env['DISPLAY'] = display xvfb_proc = start_proc(xvfb_cmd, env) atexit.register(lambda: xvfb_proc.terminate()) - ui_proc = start_proc(ui_cmd, env) atexit.register(lambda: ui_proc.terminate()) - Params().put('IsMetric', '1' if use_metric else '0') - replay_proc = start_proc(replay_cmd, env) atexit.register(lambda: replay_proc.terminate()) procs = [replay_proc, ui_proc, xvfb_proc] @@ -268,7 +281,6 @@ def main(): p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}') p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high') p.add_argument('-s', '--start', help='start clipping at seconds', type=int) - p.add_argument('-m', '--metric', help='use metric units in ui (e.g. kph)', action='store_true') p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) args = parse_args(p) try: @@ -282,7 +294,6 @@ def main(): end=args.end, target_mb=args.file_size, title=args.title, - use_metric=args.metric, ) except KeyboardInterrupt as e: logger.exception('interrupted by user', exc_info=e) From 64b0ede9ae2a02b0e8d4aa736f73e09e1f54c540 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Wed, 14 May 2025 20:17:50 +0200 Subject: [PATCH 052/142] fix: typos (#35224) * fix: typos * Update what-is-a-car-port.md --------- Co-authored-by: Adeeb Shihadeh --- docs/contributing/roadmap.md | 2 +- selfdrive/pandad/pandad.cc | 2 +- selfdrive/test/process_replay/README.md | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/contributing/roadmap.md b/docs/contributing/roadmap.md index 7086d85b85..1262017a0b 100644 --- a/docs/contributing/roadmap.md +++ b/docs/contributing/roadmap.md @@ -12,7 +12,7 @@ This is the roadmap for the next major openpilot releases. Also check out openpilot 0.10 will be the first release with a driving policy trained in a [learned simulator](https://youtu.be/EqQNZXqzFSI). -* Driving model trained in a learned simlator +* Driving model trained in a learned simulator * Always-on driver monitoring (behind a toggle) * GPS removed from the driving stack * 100KB qlogs diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index fbf4e206ae..1666c6229f 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -25,7 +25,7 @@ // - If a panda connection is dropped, pandad will reconnect to all pandas // - If a panda is added, we will only reconnect when we are offroad // CAN buses: -// - Each panda will have it's block of 4 buses. E.g.: the second panda will use +// - Each panda will have its block of 4 buses. E.g.: the second panda will use // bus numbers 4, 5, 6 and 7 // - The internal panda will always be used for accessing the OBD2 port, // and thus firmware queries diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 381e4dcb7f..dc801e4285 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -38,7 +38,7 @@ optional arguments: ## Forks -openpilot forks can use this test with their own reference logs, by default `test_proccess.py` saves logs locally. +openpilot forks can use this test with their own reference logs, by default `test_proccesses.py` saves logs locally. To generate new logs: @@ -48,13 +48,13 @@ Then, check in the new logs using git-lfs. Make sure to also update the `ref_com ## API -Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs. +Process replay test suite exposes programmatic APIs for simultaneously running processes or groups of processes on provided logs. ```py def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, *args, **kwargs) -> List[capnp._DynamicStructReader]: def replay_process( - cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None, + cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, Any]] = None, fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, disable_progress: bool = False ) -> List[capnp._DynamicStructReader]: ``` @@ -73,14 +73,14 @@ output_logs = replay_process_with_name('locationd', lr) output_logs = replay_process_with_name(['ubloxd', 'locationd'], lr) ``` -Supported processes: +Supported processes: * controlsd * radard * plannerd * calibrationd * dmonitoringd * locationd -* paramsd +* paramsd * ubloxd * torqued * modeld From f1c0109c89f8e85d23c9addd11fc7412c3ca4dc7 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 15 May 2025 02:18:08 +0800 Subject: [PATCH 053/142] system/ui: Fix crash when exiting the application with CTRL+C (KeyboardInterrupt). (#35221) handle KeyboardInterrupt --- system/ui/lib/application.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ba1441170b..fb7f7832a4 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -131,17 +131,20 @@ class GuiApplication: rl.close_window() def render(self): - while not (self._window_close_requested or rl.window_should_close()): - rl.begin_drawing() - rl.clear_background(rl.BLACK) + try: + while not (self._window_close_requested or rl.window_should_close()): + rl.begin_drawing() + rl.clear_background(rl.BLACK) - yield + yield - if DEBUG_FPS: - rl.draw_fps(10, 10) + if DEBUG_FPS: + rl.draw_fps(10, 10) - rl.end_drawing() - self._monitor_fps() + rl.end_drawing() + self._monitor_fps() + except KeyboardInterrupt: + pass def font(self, font_weight: FontWeight=FontWeight.NORMAL): return self._fonts[font_weight] From 84e9b7598346dc9970d96ca5576dac35f29ebe37 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 15 May 2025 02:20:39 +0800 Subject: [PATCH 054/142] system/ui: fix WiFi manager shutdown task errors (#35204) fix WiFi manager shutdown task errors --- system/ui/lib/wifi_manager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index e08e4cbb62..7070266aab 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -115,7 +115,7 @@ class WifiManager: except asyncio.CancelledError: pass if self.bus: - await self.bus.disconnect() + self.bus.disconnect() async def request_scan(self) -> None: try: @@ -634,8 +634,10 @@ class WifiManagerWrapper: def shutdown(self) -> None: if self._running: - if self._manager is not None: - self._run_coroutine(self._manager.shutdown()) + if self._manager is not None and self._loop: + shutdown_future = asyncio.run_coroutine_threadsafe(self._manager.shutdown(), self._loop) + shutdown_future.result(timeout=3.0) + if self._loop and self._loop.is_running(): self._loop.call_soon_threadsafe(self._loop.stop) if self._thread and self._thread.is_alive(): From 02a251f50d8dd157ec2b40c82dbff3c563eb987b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 15 May 2025 02:21:28 +0800 Subject: [PATCH 055/142] cabana: add RouteInfo dialog to view and navigate route segment details (#35134) add RouteInfo dialog to view and navigate route segment details --- tools/cabana/SConscript | 2 +- tools/cabana/tools/routeinfo.cc | 40 +++++++++++++++++++++++++++++++++ tools/cabana/tools/routeinfo.h | 8 +++++++ tools/cabana/videowidget.cc | 15 +++++++++++-- tools/cabana/videowidget.h | 1 + 5 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 tools/cabana/tools/routeinfo.cc create mode 100644 tools/cabana/tools/routeinfo.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 1cacaba4a2..c8e6093b86 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -29,7 +29,7 @@ cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcans 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', 'utils/export.cc', 'utils/util.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/tools/routeinfo.cc b/tools/cabana/tools/routeinfo.cc new file mode 100644 index 0000000000..77a0e065cd --- /dev/null +++ b/tools/cabana/tools/routeinfo.cc @@ -0,0 +1,40 @@ +#include "tools/cabana/tools/routeinfo.h" +#include +#include +#include +#include +#include "tools/cabana/streams/replaystream.h" + +RouteInfoDlg::RouteInfoDlg(QWidget *parent) : QDialog(parent) { + auto *replay = qobject_cast(can)->getReplay(); + setWindowTitle(tr("Route: %1").arg(QString::fromStdString(replay->route().name()))); + + auto *table = new QTableWidget(replay->route().segments().size(), 7, this); + table->setToolTip(tr("Click on a row to seek to the corresponding segment.")); + table->setEditTriggers(QAbstractItemView::NoEditTriggers); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSelectionMode(QAbstractItemView::SingleSelection); + table->setHorizontalHeaderLabels({"", "rlog", "fcam", "ecam", "dcam", "qlog", "qcam"}); + table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + table->verticalHeader()->setVisible(false); + table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + int row = 0; + for (const auto &[seg_num, seg] : replay->route().segments()) { + table->setItem(row, 0, new QTableWidgetItem(QString::number(seg_num))); + table->setItem(row, 1, new QTableWidgetItem(seg.rlog.empty() ? "--" : "Yes")); + table->setItem(row, 2, new QTableWidgetItem(seg.road_cam.empty() ? "--" : "Yes")); + table->setItem(row, 3, new QTableWidgetItem(seg.wide_road_cam.empty() ? "--" : "Yes")); + table->setItem(row, 4, new QTableWidgetItem(seg.driver_cam.empty() ? "--" : "Yes")); + table->setItem(row, 5, new QTableWidgetItem(seg.qlog.empty() ? "--" : "Yes")); + table->setItem(row, 6, new QTableWidgetItem(seg.qcamera.empty() ? "--" : "Yes")); + ++row; + } + table->setMinimumWidth(table->horizontalHeader()->length() + table->verticalScrollBar()->sizeHint().width()); + table->setMinimumHeight(table->rowHeight(0) * std::min(table->rowCount(), 13) + table->horizontalHeader()->height() + table->frameWidth() * 2); + + connect(table, &QTableWidget::itemClicked, [](QTableWidgetItem *item) { can->seekTo(item->row() * 60.0); }); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(table); +} diff --git a/tools/cabana/tools/routeinfo.h b/tools/cabana/tools/routeinfo.h new file mode 100644 index 0000000000..36f32b4bf4 --- /dev/null +++ b/tools/cabana/tools/routeinfo.h @@ -0,0 +1,8 @@ +#pragma once +#include + +class RouteInfoDlg : public QDialog { + Q_OBJECT +public: + RouteInfoDlg(QWidget *parent = nullptr); +}; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index e792f80a41..3d715b0319 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -11,6 +11,8 @@ #include #include +#include "tools/cabana/tools/routeinfo.h" + const int MIN_VIDEO_HEIGHT = 100; const int THUMBNAIL_MARGIN = 3; @@ -100,9 +102,12 @@ void VideoWidget::createPlaybackController() { if (!can->liveStreaming()) { toolbar->addAction(utils::icon("repeat"), tr("Loop playback"), this, &VideoWidget::loopPlaybackClicked); + createSpeedDropdown(toolbar); + toolbar->addSeparator(); + toolbar->addAction(utils::icon("info-circle"), tr("View route details"), this, &VideoWidget::showRouteInfo); + } else { + createSpeedDropdown(toolbar); } - - createSpeedDropdown(toolbar); } void VideoWidget::createSpeedDropdown(QToolBar *toolbar) { @@ -230,6 +235,12 @@ void VideoWidget::showThumbnail(double seconds) { slider->update(); } +void VideoWidget::showRouteInfo() { + RouteInfoDlg *route_info = new RouteInfoDlg(this); + route_info->setAttribute(Qt::WA_DeleteOnClose); + route_info->show(); +} + bool VideoWidget::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseMove) { auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 1d87a5cfa0..6f756448c0 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -71,6 +71,7 @@ protected: void createSpeedDropdown(QToolBar *toolbar); void loopPlaybackClicked(); void vipcAvailableStreamsUpdated(std::set streams); + void showRouteInfo(); StreamCameraView *cam_widget; QAction *time_display_action = nullptr; From 2ad501cc946b666a2439eb81417bf34d48dcdeeb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 15 May 2025 02:21:36 +0800 Subject: [PATCH 056/142] common/params.cc: Handle EINTR for fsync in Params::put to Improve Robustness (#35142) Handle EINTR for fsync in Params::put to Improve Robustness --- common/params.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/params.cc b/common/params.cc index d1e5a36462..337be814b7 100644 --- a/common/params.cc +++ b/common/params.cc @@ -140,7 +140,7 @@ int Params::put(const char* key, const char* value, size_t value_size) { } // fsync to force persist the changes. - if ((result = fsync(tmp_fd)) < 0) break; + if ((result = HANDLE_EINTR(fsync(tmp_fd))) < 0) break; FileLock file_lock(params_path + "/.lock"); From e8394a27d7e7cad1c63c612a8a4c4bb5806321f9 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 14 May 2025 14:25:27 -0700 Subject: [PATCH 057/142] fix(clip): properly set exit code (#35225) fix: properly set exit code --- tools/clip/run.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/clip/run.py b/tools/clip/run.py index 87a5d79d2e..fce1b02e98 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -5,6 +5,7 @@ import logging import os import platform import shutil +import sys import time from argparse import ArgumentParser, ArgumentTypeError from collections.abc import Sequence @@ -283,6 +284,7 @@ def main(): p.add_argument('-s', '--start', help='start clipping at seconds', type=int) p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) args = parse_args(p) + exit_code = 1 try: clip( data_dir=args.data_dir, @@ -295,12 +297,14 @@ def main(): target_mb=args.file_size, title=args.title, ) + exit_code = 0 except KeyboardInterrupt as e: logger.exception('interrupted by user', exc_info=e) except Exception as e: logger.exception('encountered error', exc_info=e) finally: atexit._run_exitfuncs() + sys.exit(exit_code) if __name__ == '__main__': From 33c164629f2270ce3ee40c260231f9deb4ea13cc Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 14 May 2025 15:44:57 -0700 Subject: [PATCH 058/142] fix(clip): ignore unknown Params keys (#35226) * fix: ignore params configured by forks * comment * dont bother logging count or in normal flow * better log --- tools/clip/run.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index fce1b02e98..fb9ffbbaee 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -16,7 +16,7 @@ from typing import Literal from cereal.messaging import SubMaster from openpilot.common.basedir import BASEDIR -from openpilot.common.params import Params +from openpilot.common.params import Params, UnknownKeyName from openpilot.common.prefix import OpenpilotPrefix from openpilot.tools.lib.route import Route from openpilot.tools.lib.logreader import LogReader, ReadMode, comma_api_source @@ -126,8 +126,12 @@ def populate_car_params(route: Route): entries = init_data.params.entries for cp in entries: key, value = cp.key, cp.value - params.put(key, value) - logger.info(f'persisted {len(entries)} CarParam(s)') + try: + params.put(key, value) + except UnknownKeyName: + # forks of openpilot may have other Params keys configured. ignore these + logger.warning(f"unknown Params key '{key}', skipping") + logger.debug('persisted CarParams') def start_proc(args: list[str], env: dict[str, str]): From 2af3cc6093d461096c876882250216985c7bf421 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 14 May 2025 19:31:55 -0700 Subject: [PATCH 059/142] joystick: use ISO acceleration (#35230) ISO max --- tools/joystick/joystickd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index b775f18f70..673a5bc1d0 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -10,7 +10,7 @@ from openpilot.common.params import Params from openpilot.common.swaglog import cloudlog LongCtrlState = car.CarControl.Actuators.LongControlState -MAX_LAT_ACCEL = 2.5 +MAX_LAT_ACCEL = 3.0 def joystickd_thread(): From 5d8c8ae911f9e1bf1f91dc096bf0a67efef0d9e0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 14 May 2025 19:51:12 -0700 Subject: [PATCH 060/142] Tesla: raised lateral limits (#35231) * bump * bump * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 7faa3a0811..f960080e28 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 7faa3a0811ddc7bf7ba38f1c8e2d1b6c17342a30 +Subproject commit f960080e286401cdb432709589d582a6c065fd10 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index ab602d4726..c97f9a6bde 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -61bfff810e45b7a1577cfbb572a7b3c233f8551b \ No newline at end of file +bc0a2e6eac422e574b6c670fe493d368df10ef42 \ No newline at end of file From 0fec3656a1a4d619d70920f7e1f36cd63271fdc2 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Thu, 15 May 2025 06:18:58 -0700 Subject: [PATCH 061/142] feat(clip): faster qlog (#35227) * feat(clip): faster qlog * simpler * imports --- tools/clip/run.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index fb9ffbbaee..500c5e7b40 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -19,7 +19,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params, UnknownKeyName from openpilot.common.prefix import OpenpilotPrefix from openpilot.tools.lib.route import Route -from openpilot.tools.lib.logreader import LogReader, ReadMode, comma_api_source +from openpilot.tools.lib.logreader import LogReader DEFAULT_OUTPUT = 'output.mp4' DEMO_START = 90 @@ -117,8 +117,7 @@ def parse_args(parser: ArgumentParser): def populate_car_params(route: Route): - segment = route.name.canonical_name + '/0' # only get first segment qlog - lr = LogReader(segment, default_mode=ReadMode.QLOG, source=comma_api_source) + lr = LogReader(route.qlog_paths()[0] if len(route.qlog_paths()) else route.name.canonical_name) init_data = lr.first('initData') assert init_data is not None From 6c03e9472daa49bfd20c9cebaa5c4ffdc8c0cfcd Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 15 May 2025 16:42:32 +0100 Subject: [PATCH 062/142] system/ui: disabled button styles (#35236) --- system/ui/lib/button.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 1857fd21cc..dfe75d88bd 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -49,6 +49,9 @@ def gui_button( ) -> int: result = 0 + if button_style in (ButtonStyle.PRIMARY, ButtonStyle.DANGER) and not is_enabled: + button_style = ButtonStyle.NORMAL + # Set background color based on button type bg_color = BUTTON_BACKGROUND_COLORS[button_style] if is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect): From 735c227f3cc668c70af5a3817b1cf063261f880a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 15 May 2025 09:53:20 -0700 Subject: [PATCH 063/142] svg -> png script --- selfdrive/assets/{strip-svg-metadata.sh => prep-svg.sh} | 4 ++++ 1 file changed, 4 insertions(+) rename selfdrive/assets/{strip-svg-metadata.sh => prep-svg.sh} (67%) diff --git a/selfdrive/assets/strip-svg-metadata.sh b/selfdrive/assets/prep-svg.sh similarity index 67% rename from selfdrive/assets/strip-svg-metadata.sh rename to selfdrive/assets/prep-svg.sh index e51dc9481d..a884d734d0 100755 --- a/selfdrive/assets/strip-svg-metadata.sh +++ b/selfdrive/assets/prep-svg.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -e # sudo apt install scour @@ -6,4 +7,7 @@ for svg in $(find icons/ -type f | grep svg$); do # scour doesn't support overwriting input file scour $svg --remove-metadata $svg.tmp mv $svg.tmp $svg + + # convert to PNG + convert -background none -resize 400% -density 384 $svg "${svg%.svg}.png" done From 60113632b4cbada5b3065540e2c8bc2fd88207e6 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Thu, 15 May 2025 10:02:46 -0700 Subject: [PATCH 064/142] feat(clip): delete get_metadata call (#35229) * feat: delete get_metadata call * no one else is using this now --- tools/clip/run.py | 31 ++++++++++++++++++------------- tools/lib/route.py | 7 ------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 500c5e7b40..f0c4369c6f 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -64,18 +64,23 @@ def escape_ffmpeg_text(value: str): return value -def get_meta_text(route: Route): - metadata = route.get_metadata() - origin_parts = metadata['git_remote'].split('/') +def get_logreader(route: Route): + return LogReader(route.qlog_paths()[0] if len(route.qlog_paths()) else route.name.canonical_name) + + +def get_meta_text(lr: LogReader, route: Route): + init_data = lr.first('initData') + car_params = lr.first('carParams') + origin_parts = init_data.gitRemote.split('/') origin = origin_parts[3] if len(origin_parts) > 3 else 'unknown' return ', '.join([ - f"openpilot v{metadata['version']}", - f"route: {metadata['fullname']}", - f"car: {metadata['platform']}", + f"openpilot v{init_data.version}", + f"route: {route.name.canonical_name}", + f"car: {car_params.carFingerprint}", f"origin: {origin}", - f"branch: {metadata['git_branch']}", - f"commit: {metadata['git_commit'][:7]}", - f"modified: {str(metadata['git_dirty']).lower()}", + f"branch: {init_data.gitBranch}", + f"commit: {init_data.gitCommit[:7]}", + f"modified: {str(init_data.dirty).lower()}", ]) @@ -116,8 +121,7 @@ def parse_args(parser: ArgumentParser): return args -def populate_car_params(route: Route): - lr = LogReader(route.qlog_paths()[0] if len(route.qlog_paths()) else route.name.canonical_name) +def populate_car_params(lr: LogReader): init_data = lr.first('initData') assert init_data is not None @@ -188,6 +192,7 @@ def clip( title: str | None, ): logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB') + lr = get_logreader(route) begin_at = max(start - SECONDS_TO_WARM, 0) duration = end - start @@ -197,7 +202,7 @@ def clip( display = f':{randint(99, 999)}' box_style = 'box=1:boxcolor=black@0.33:boxborderw=7' - meta_text = get_meta_text(route) + meta_text = get_meta_text(lr, route) overlays = [ # metadata overlay f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=15:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", @@ -240,7 +245,7 @@ def clip( xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}'] with OpenpilotPrefix(prefix, shared_download_cache=True): - populate_car_params(route) + populate_car_params(lr) env = os.environ.copy() env['DISPLAY'] = display diff --git a/tools/lib/route.py b/tools/lib/route.py index 831a1e2ee5..ddb8b0486e 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -21,7 +21,6 @@ class Route: def __init__(self, name, data_dir=None): self._name = RouteName(name) self.files = None - self.metadata = None if data_dir is not None: self._segments = self._get_segments_local(data_dir) else: @@ -60,12 +59,6 @@ class Route: qcamera_path_by_seg_num = {s.name.segment_num: s.qcamera_path for s in self._segments} return [qcamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number + 1)] - def get_metadata(self): - if not self.metadata: - api = CommaApi(get_token()) - self.metadata = api.get('v1/route/' + self.name.canonical_name) - return self.metadata - # TODO: refactor this, it's super repetitive def _get_segments_remote(self): api = CommaApi(get_token()) From ec208f2d3d38fae90f988fdf92362605c3892e48 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 00:26:13 +0100 Subject: [PATCH 065/142] cleanup selfdrive/assets (#35241) * remove unused assets * these are icons * these are images * fix * fix * Revert "remove unused assets" This reverts commit 95d1a800de7e830df15d88e8e329aeadd4548b2f. * move these too --- selfdrive/assets/assets.qrc | 24 +++++++++---------- .../calibration.png} | 0 .../checkmark.svg} | 0 .../chevron_right.png} | 0 .../chffr_wheel.png} | 0 .../circled_check.svg} | 0 .../circled_slash.svg} | 0 .../icon_close.svg => icons/close2.svg} | 0 .../assets/{img_couch.svg => icons/couch.svg} | 0 .../disengage_on_accelerator.svg} | 0 .../driver_face.png} | 0 .../experimental.svg} | 0 .../experimental_grey.svg} | 0 .../experimental_white.svg} | 0 .../eye_closed.svg} | 0 .../{img_eye_open.svg => icons/eye_open.svg} | 0 .../lock_closed.svg} | 0 .../{offroad/icon_menu.png => icons/menu.png} | 0 .../icon_metric.png => icons/metric.png} | 0 .../icon_minus.png => icons/minus.png} | 0 .../monitoring.png} | 0 .../icon_network.png => icons/network.png} | 0 .../{offroad/icon_road.png => icons/road.png} | 0 .../icon_settings.png => icons/settings.png} | 0 .../icon_shell.png => icons/shell.png} | 0 .../speed_limit.png} | 0 .../assets/{images => icons}/triangle.svg | 0 .../icon_warning.png => icons/warning.png} | 0 .../wifi_strength_full.svg} | 0 .../wifi_strength_high.svg} | 0 .../wifi_strength_low.svg} | 0 .../wifi_strength_medium.svg} | 0 .../wifi_uploading.svg} | 0 .../button_continue_triangle.svg} | 0 .../spinner_comma.png} | 0 .../spinner_track.png} | 0 selfdrive/ui/qt/network/networking.cc | 8 +++---- selfdrive/ui/qt/offroad/experimental_mode.cc | 4 ++-- selfdrive/ui/qt/offroad/settings.cc | 18 +++++++------- selfdrive/ui/qt/onroad/buttons.cc | 4 ++-- selfdrive/ui/qt/onroad/driver_monitoring.cc | 2 +- selfdrive/ui/qt/setup/setup.cc | 6 ++--- selfdrive/ui/qt/widgets/input.cc | 4 ++-- system/ui/spinner.py | 4 ++-- 44 files changed, 37 insertions(+), 37 deletions(-) rename selfdrive/assets/{offroad/icon_calibration.png => icons/calibration.png} (100%) rename selfdrive/assets/{offroad/icon_checkmark.svg => icons/checkmark.svg} (100%) rename selfdrive/assets/{offroad/icon_chevron_right.png => icons/chevron_right.png} (100%) rename selfdrive/assets/{img_chffr_wheel.png => icons/chffr_wheel.png} (100%) rename selfdrive/assets/{img_circled_check.svg => icons/circled_check.svg} (100%) rename selfdrive/assets/{img_circled_slash.svg => icons/circled_slash.svg} (100%) rename selfdrive/assets/{offroad/icon_close.svg => icons/close2.svg} (100%) rename selfdrive/assets/{img_couch.svg => icons/couch.svg} (100%) rename selfdrive/assets/{offroad/icon_disengage_on_accelerator.svg => icons/disengage_on_accelerator.svg} (100%) rename selfdrive/assets/{img_driver_face.png => icons/driver_face.png} (100%) rename selfdrive/assets/{img_experimental.svg => icons/experimental.svg} (100%) rename selfdrive/assets/{img_experimental_grey.svg => icons/experimental_grey.svg} (100%) rename selfdrive/assets/{img_experimental_white.svg => icons/experimental_white.svg} (100%) rename selfdrive/assets/{img_eye_closed.svg => icons/eye_closed.svg} (100%) rename selfdrive/assets/{img_eye_open.svg => icons/eye_open.svg} (100%) rename selfdrive/assets/{offroad/icon_lock_closed.svg => icons/lock_closed.svg} (100%) rename selfdrive/assets/{offroad/icon_menu.png => icons/menu.png} (100%) rename selfdrive/assets/{offroad/icon_metric.png => icons/metric.png} (100%) rename selfdrive/assets/{offroad/icon_minus.png => icons/minus.png} (100%) rename selfdrive/assets/{offroad/icon_monitoring.png => icons/monitoring.png} (100%) rename selfdrive/assets/{offroad/icon_network.png => icons/network.png} (100%) rename selfdrive/assets/{offroad/icon_road.png => icons/road.png} (100%) rename selfdrive/assets/{offroad/icon_settings.png => icons/settings.png} (100%) rename selfdrive/assets/{offroad/icon_shell.png => icons/shell.png} (100%) rename selfdrive/assets/{offroad/icon_speed_limit.png => icons/speed_limit.png} (100%) rename selfdrive/assets/{images => icons}/triangle.svg (100%) rename selfdrive/assets/{offroad/icon_warning.png => icons/warning.png} (100%) rename selfdrive/assets/{offroad/icon_wifi_strength_full.svg => icons/wifi_strength_full.svg} (100%) rename selfdrive/assets/{offroad/icon_wifi_strength_high.svg => icons/wifi_strength_high.svg} (100%) rename selfdrive/assets/{offroad/icon_wifi_strength_low.svg => icons/wifi_strength_low.svg} (100%) rename selfdrive/assets/{offroad/icon_wifi_strength_medium.svg => icons/wifi_strength_medium.svg} (100%) rename selfdrive/assets/{offroad/icon_wifi_uploading.svg => icons/wifi_uploading.svg} (100%) rename selfdrive/assets/{img_continue_triangle.svg => images/button_continue_triangle.svg} (100%) rename selfdrive/assets/{img_spinner_comma.png => images/spinner_comma.png} (100%) rename selfdrive/assets/{img_spinner_track.png => images/spinner_track.png} (100%) diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc index 6d8d8df334..26a7d998ed 100644 --- a/selfdrive/assets/assets.qrc +++ b/selfdrive/assets/assets.qrc @@ -1,19 +1,19 @@ ../../third_party/bootstrap/bootstrap-icons.svg - img_continue_triangle.svg - img_circled_check.svg - img_circled_slash.svg - img_eye_open.svg - img_eye_closed.svg + images/button_continue_triangle.svg + icons/circled_check.svg + icons/circled_slash.svg + icons/eye_open.svg + icons/eye_closed.svg icons/close.svg - offroad/icon_lock_closed.svg - offroad/icon_checkmark.svg - offroad/icon_warning.png - offroad/icon_wifi_strength_low.svg - offroad/icon_wifi_strength_medium.svg - offroad/icon_wifi_strength_high.svg - offroad/icon_wifi_strength_full.svg + icons/lock_closed.svg + icons/checkmark.svg + icons/warning.png + icons/wifi_strength_low.svg + icons/wifi_strength_medium.svg + icons/wifi_strength_high.svg + icons/wifi_strength_full.svg ../ui/translations/languages.json diff --git a/selfdrive/assets/offroad/icon_calibration.png b/selfdrive/assets/icons/calibration.png similarity index 100% rename from selfdrive/assets/offroad/icon_calibration.png rename to selfdrive/assets/icons/calibration.png diff --git a/selfdrive/assets/offroad/icon_checkmark.svg b/selfdrive/assets/icons/checkmark.svg similarity index 100% rename from selfdrive/assets/offroad/icon_checkmark.svg rename to selfdrive/assets/icons/checkmark.svg diff --git a/selfdrive/assets/offroad/icon_chevron_right.png b/selfdrive/assets/icons/chevron_right.png similarity index 100% rename from selfdrive/assets/offroad/icon_chevron_right.png rename to selfdrive/assets/icons/chevron_right.png diff --git a/selfdrive/assets/img_chffr_wheel.png b/selfdrive/assets/icons/chffr_wheel.png similarity index 100% rename from selfdrive/assets/img_chffr_wheel.png rename to selfdrive/assets/icons/chffr_wheel.png diff --git a/selfdrive/assets/img_circled_check.svg b/selfdrive/assets/icons/circled_check.svg similarity index 100% rename from selfdrive/assets/img_circled_check.svg rename to selfdrive/assets/icons/circled_check.svg diff --git a/selfdrive/assets/img_circled_slash.svg b/selfdrive/assets/icons/circled_slash.svg similarity index 100% rename from selfdrive/assets/img_circled_slash.svg rename to selfdrive/assets/icons/circled_slash.svg diff --git a/selfdrive/assets/offroad/icon_close.svg b/selfdrive/assets/icons/close2.svg similarity index 100% rename from selfdrive/assets/offroad/icon_close.svg rename to selfdrive/assets/icons/close2.svg diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/icons/couch.svg similarity index 100% rename from selfdrive/assets/img_couch.svg rename to selfdrive/assets/icons/couch.svg diff --git a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg b/selfdrive/assets/icons/disengage_on_accelerator.svg similarity index 100% rename from selfdrive/assets/offroad/icon_disengage_on_accelerator.svg rename to selfdrive/assets/icons/disengage_on_accelerator.svg diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/icons/driver_face.png similarity index 100% rename from selfdrive/assets/img_driver_face.png rename to selfdrive/assets/icons/driver_face.png diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/icons/experimental.svg similarity index 100% rename from selfdrive/assets/img_experimental.svg rename to selfdrive/assets/icons/experimental.svg diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/icons/experimental_grey.svg similarity index 100% rename from selfdrive/assets/img_experimental_grey.svg rename to selfdrive/assets/icons/experimental_grey.svg diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/icons/experimental_white.svg similarity index 100% rename from selfdrive/assets/img_experimental_white.svg rename to selfdrive/assets/icons/experimental_white.svg diff --git a/selfdrive/assets/img_eye_closed.svg b/selfdrive/assets/icons/eye_closed.svg similarity index 100% rename from selfdrive/assets/img_eye_closed.svg rename to selfdrive/assets/icons/eye_closed.svg diff --git a/selfdrive/assets/img_eye_open.svg b/selfdrive/assets/icons/eye_open.svg similarity index 100% rename from selfdrive/assets/img_eye_open.svg rename to selfdrive/assets/icons/eye_open.svg diff --git a/selfdrive/assets/offroad/icon_lock_closed.svg b/selfdrive/assets/icons/lock_closed.svg similarity index 100% rename from selfdrive/assets/offroad/icon_lock_closed.svg rename to selfdrive/assets/icons/lock_closed.svg diff --git a/selfdrive/assets/offroad/icon_menu.png b/selfdrive/assets/icons/menu.png similarity index 100% rename from selfdrive/assets/offroad/icon_menu.png rename to selfdrive/assets/icons/menu.png diff --git a/selfdrive/assets/offroad/icon_metric.png b/selfdrive/assets/icons/metric.png similarity index 100% rename from selfdrive/assets/offroad/icon_metric.png rename to selfdrive/assets/icons/metric.png diff --git a/selfdrive/assets/offroad/icon_minus.png b/selfdrive/assets/icons/minus.png similarity index 100% rename from selfdrive/assets/offroad/icon_minus.png rename to selfdrive/assets/icons/minus.png diff --git a/selfdrive/assets/offroad/icon_monitoring.png b/selfdrive/assets/icons/monitoring.png similarity index 100% rename from selfdrive/assets/offroad/icon_monitoring.png rename to selfdrive/assets/icons/monitoring.png diff --git a/selfdrive/assets/offroad/icon_network.png b/selfdrive/assets/icons/network.png similarity index 100% rename from selfdrive/assets/offroad/icon_network.png rename to selfdrive/assets/icons/network.png diff --git a/selfdrive/assets/offroad/icon_road.png b/selfdrive/assets/icons/road.png similarity index 100% rename from selfdrive/assets/offroad/icon_road.png rename to selfdrive/assets/icons/road.png diff --git a/selfdrive/assets/offroad/icon_settings.png b/selfdrive/assets/icons/settings.png similarity index 100% rename from selfdrive/assets/offroad/icon_settings.png rename to selfdrive/assets/icons/settings.png diff --git a/selfdrive/assets/offroad/icon_shell.png b/selfdrive/assets/icons/shell.png similarity index 100% rename from selfdrive/assets/offroad/icon_shell.png rename to selfdrive/assets/icons/shell.png diff --git a/selfdrive/assets/offroad/icon_speed_limit.png b/selfdrive/assets/icons/speed_limit.png similarity index 100% rename from selfdrive/assets/offroad/icon_speed_limit.png rename to selfdrive/assets/icons/speed_limit.png diff --git a/selfdrive/assets/images/triangle.svg b/selfdrive/assets/icons/triangle.svg similarity index 100% rename from selfdrive/assets/images/triangle.svg rename to selfdrive/assets/icons/triangle.svg diff --git a/selfdrive/assets/offroad/icon_warning.png b/selfdrive/assets/icons/warning.png similarity index 100% rename from selfdrive/assets/offroad/icon_warning.png rename to selfdrive/assets/icons/warning.png diff --git a/selfdrive/assets/offroad/icon_wifi_strength_full.svg b/selfdrive/assets/icons/wifi_strength_full.svg similarity index 100% rename from selfdrive/assets/offroad/icon_wifi_strength_full.svg rename to selfdrive/assets/icons/wifi_strength_full.svg diff --git a/selfdrive/assets/offroad/icon_wifi_strength_high.svg b/selfdrive/assets/icons/wifi_strength_high.svg similarity index 100% rename from selfdrive/assets/offroad/icon_wifi_strength_high.svg rename to selfdrive/assets/icons/wifi_strength_high.svg diff --git a/selfdrive/assets/offroad/icon_wifi_strength_low.svg b/selfdrive/assets/icons/wifi_strength_low.svg similarity index 100% rename from selfdrive/assets/offroad/icon_wifi_strength_low.svg rename to selfdrive/assets/icons/wifi_strength_low.svg diff --git a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg b/selfdrive/assets/icons/wifi_strength_medium.svg similarity index 100% rename from selfdrive/assets/offroad/icon_wifi_strength_medium.svg rename to selfdrive/assets/icons/wifi_strength_medium.svg diff --git a/selfdrive/assets/offroad/icon_wifi_uploading.svg b/selfdrive/assets/icons/wifi_uploading.svg similarity index 100% rename from selfdrive/assets/offroad/icon_wifi_uploading.svg rename to selfdrive/assets/icons/wifi_uploading.svg diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/images/button_continue_triangle.svg similarity index 100% rename from selfdrive/assets/img_continue_triangle.svg rename to selfdrive/assets/images/button_continue_triangle.svg diff --git a/selfdrive/assets/img_spinner_comma.png b/selfdrive/assets/images/spinner_comma.png similarity index 100% rename from selfdrive/assets/img_spinner_comma.png rename to selfdrive/assets/images/spinner_comma.png diff --git a/selfdrive/assets/img_spinner_track.png b/selfdrive/assets/images/spinner_track.png similarity index 100% rename from selfdrive/assets/img_spinner_track.png rename to selfdrive/assets/images/spinner_track.png diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 066dc3ca7e..93f7ff3f21 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -234,12 +234,12 @@ WifiUI::WifiUI(QWidget *parent, WifiManager* wifi) : QWidget(parent), wifi(wifi) // load imgs for (const auto &s : {"low", "medium", "high", "full"}) { - QPixmap pix(ASSET_PATH + "/offroad/icon_wifi_strength_" + s + ".svg"); + QPixmap pix(ASSET_PATH + "/icons/wifi_strength_" + s + ".svg"); strengths.push_back(pix.scaledToHeight(68, Qt::SmoothTransformation)); } - lock = QPixmap(ASSET_PATH + "offroad/icon_lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); - checkmark = QPixmap(ASSET_PATH + "offroad/icon_checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); - circled_slash = QPixmap(ASSET_PATH + "img_circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + lock = QPixmap(ASSET_PATH + "icons/lock_closed.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + checkmark = QPixmap(ASSET_PATH + "icons/checkmark.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); + circled_slash = QPixmap(ASSET_PATH + "icons/circled_slash.svg").scaledToWidth(ICON_WIDTH, Qt::SmoothTransformation); scanningLabel = new QLabel(tr("Scanning for networks...")); scanningLabel->setStyleSheet("font-size: 65px;"); diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index b99220c1d1..e255073f39 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -9,8 +9,8 @@ #include "selfdrive/ui/ui.h" ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { - chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); - experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + chill_pixmap = QPixmap("../assets/icons/couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + experimental_pixmap = QPixmap("../assets/icons/experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); // go to toggles and expand experimental mode description connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); }); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 2057f1223e..ed06734f0f 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -23,43 +23,43 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "OpenpilotEnabledToggle", tr("Enable openpilot"), tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), - "../assets/img_chffr_wheel.png", + "../assets/icons/chffr_wheel.png", }, { "ExperimentalMode", tr("Experimental Mode"), "", - "../assets/img_experimental_white.svg", + "../assets/icons/experimental_white.svg", }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), - "../assets/offroad/icon_disengage_on_accelerator.svg", + "../assets/icons/disengage_on_accelerator.svg", }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), tr("Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h)."), - "../assets/offroad/icon_warning.png", + "../assets/icons/warning.png", }, { "AlwaysOnDM", tr("Always-On Driver Monitoring"), tr("Enable driver monitoring even when openpilot is not engaged."), - "../assets/offroad/icon_monitoring.png", + "../assets/icons/monitoring.png", }, { "RecordFront", tr("Record and Upload Driver Camera"), tr("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), - "../assets/offroad/icon_monitoring.png", + "../assets/icons/monitoring.png", }, { "IsMetric", tr("Use Metric System"), tr("Display speed in km/h instead of mph."), - "../assets/offroad/icon_metric.png", + "../assets/icons/metric.png", }, }; @@ -69,7 +69,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. " "In relaxed mode openpilot will stay further away from lead cars. On supported cars, you can cycle through these personalities with " "your steering wheel distance button."), - "../assets/offroad/icon_speed_limit.png", + "../assets/icons/speed_limit.png", longi_button_texts); // set up uiState update for personality setting @@ -91,7 +91,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { } // Toggles with confirmation dialogs - toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); + toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg"); toggles["ExperimentalMode"]->setConfirmation(true, true); } diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 2c2cc672b9..32e58c9dba 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -19,8 +19,8 @@ void drawIcon(QPainter &p, const QPoint ¢er, const QPixmap &img, const QBrus ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(false), engageable(false), QPushButton(parent) { setFixedSize(btn_size, btn_size); - engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); - experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); + engage_img = loadPixmap("../assets/icons/chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/icons/experimental.svg", {img_size, img_size}); QObject::connect(this, &QPushButton::clicked, this, &ExperimentalButton::changeMode); } diff --git a/selfdrive/ui/qt/onroad/driver_monitoring.cc b/selfdrive/ui/qt/onroad/driver_monitoring.cc index afd003cf8f..49f2c950b4 100644 --- a/selfdrive/ui/qt/onroad/driver_monitoring.cc +++ b/selfdrive/ui/qt/onroad/driver_monitoring.cc @@ -21,7 +21,7 @@ static const QColor DMON_ENGAGED_COLOR = QColor::fromRgbF(0.1, 0.945, 0.26); static const QColor DMON_DISENGAGED_COLOR = QColor::fromRgbF(0.545, 0.545, 0.545); DriverMonitorRenderer::DriverMonitorRenderer() : face_kpts_draw(std::size(DEFAULT_FACE_KPTS_3D)) { - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); + dm_img = loadPixmap("../assets/icons/driver_face.png", {img_size + 5, img_size + 5}); } void DriverMonitorRenderer::updateState(const UIState &s) { diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index dfa9e12b4e..8a06c9fda0 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -98,7 +98,7 @@ QWidget * Setup::low_voltage() { main_layout->addLayout(inner_layout); QLabel *triangle = new QLabel(); - triangle->setPixmap(QPixmap(ASSET_PATH + "offroad/icon_warning.png")); + triangle->setPixmap(QPixmap(ASSET_PATH + "icons/warning.png")); inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); inner_layout->addSpacing(80); @@ -159,7 +159,7 @@ QWidget * Setup::getting_started() { vlayout->addStretch(); QPushButton *btn = new QPushButton(); - btn->setIcon(QIcon(":/img_continue_triangle.svg")); + btn->setIcon(QIcon(":/images/button_continue_triangle.svg")); btn->setIconSize(QSize(54, 106)); btn->setFixedSize(310, 1080); btn->setProperty("primary", true); @@ -251,7 +251,7 @@ QWidget * radio_button(QString title, QButtonGroup *group) { )"); // checkmark icon - QPixmap pix(":/img_circled_check.svg"); + QPixmap pix(":/icons/circled_check.svg"); btn->setIcon(pix); btn->setIconSize(QSize(0, 0)); btn->setLayoutDirection(Qt::RightToLeft); diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index b0b6b4c23b..0cbf14931b 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -120,11 +120,11 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s eye_btn->setFixedSize(150, 120); QObject::connect(eye_btn, &QPushButton::toggled, [=](bool checked) { if (checked) { - eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_closed.svg")); + eye_btn->setIcon(QIcon(ASSET_PATH + "icons/eye_closed.svg")); eye_btn->setIconSize(QSize(81, 54)); line->setEchoMode(QLineEdit::Password); } else { - eye_btn->setIcon(QIcon(ASSET_PATH + "img_eye_open.svg")); + eye_btn->setIcon(QIcon(ASSET_PATH + "icons/eye_open.svg")); eye_btn->setIconSize(QSize(81, 44)); line->setEchoMode(QLineEdit::Normal); } diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 93bce3887e..5fd3f964a6 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -24,8 +24,8 @@ def clamp(value, min_value, max_value): class SpinnerRenderer: def __init__(self): - self._comma_texture = gui_app.texture("img_spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE) - self._spinner_texture = gui_app.texture("img_spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True) + self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE) + self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True) self._rotation = 0.0 self._progress: int | None = None self._wrapped_lines: list[str] = [] From 62f5a59f7714b7d1c44a85f3e8fd06a7ce159502 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 00:52:49 +0100 Subject: [PATCH 066/142] selfdrive/assets: optimize SVGs (#35242) --- selfdrive/assets/icons/checkmark.svg | 4 ++-- selfdrive/assets/icons/circled_check.svg | 4 ++-- selfdrive/assets/icons/circled_slash.svg | 4 ++-- selfdrive/assets/icons/close.svg | 4 ++-- selfdrive/assets/icons/close2.svg | 4 ++-- selfdrive/assets/icons/couch.svg | 4 ++-- selfdrive/assets/icons/disengage_on_accelerator.svg | 4 ++-- selfdrive/assets/icons/experimental.svg | 4 ++-- selfdrive/assets/icons/experimental_grey.svg | 4 ++-- selfdrive/assets/icons/experimental_white.svg | 4 ++-- selfdrive/assets/icons/eye_closed.svg | 4 ++-- selfdrive/assets/icons/eye_open.svg | 4 ++-- selfdrive/assets/icons/lock_closed.svg | 4 ++-- selfdrive/assets/icons/triangle.svg | 4 ++-- selfdrive/assets/icons/wifi_strength_full.svg | 4 ++-- selfdrive/assets/icons/wifi_strength_high.svg | 4 ++-- selfdrive/assets/icons/wifi_strength_low.svg | 4 ++-- selfdrive/assets/icons/wifi_strength_medium.svg | 4 ++-- selfdrive/assets/icons/wifi_uploading.svg | 4 ++-- selfdrive/assets/images/button_continue_triangle.svg | 4 ++-- selfdrive/assets/prep-svg.sh | 9 +++------ 21 files changed, 43 insertions(+), 46 deletions(-) diff --git a/selfdrive/assets/icons/checkmark.svg b/selfdrive/assets/icons/checkmark.svg index 7a1db13497..26480698cd 100644 --- a/selfdrive/assets/icons/checkmark.svg +++ b/selfdrive/assets/icons/checkmark.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da1540859c4c42878a32a0a81a38ca38b04be4cb0fe46709df3e024b7f3b4036 -size 243 +oid sha256:17a135c18634647a73734300f5d0ad98082b07779b5553e72b99686857380ee7 +size 244 diff --git a/selfdrive/assets/icons/circled_check.svg b/selfdrive/assets/icons/circled_check.svg index 1852ba947a..aab06ec1e0 100644 --- a/selfdrive/assets/icons/circled_check.svg +++ b/selfdrive/assets/icons/circled_check.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5687faf4cb22bece0405893671651eb13e8eda393987610f493ee5e05eac61d -size 439 +oid sha256:5c88458f6326265965626cbc97c2219bd513b15a6468b08f80b43ef014b7904b +size 372 diff --git a/selfdrive/assets/icons/circled_slash.svg b/selfdrive/assets/icons/circled_slash.svg index 6c77030d15..89c6e2b1ae 100644 --- a/selfdrive/assets/icons/circled_slash.svg +++ b/selfdrive/assets/icons/circled_slash.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbec38447732a443c304042c3c3d362c94e11e794e7cc7dc86aa1c7bba16c6b6 -size 328 +oid sha256:4d91c86028af58cbe2770799d0fe7e55d143f9b3f67fdebcb47d328d6e410285 +size 223 diff --git a/selfdrive/assets/icons/close.svg b/selfdrive/assets/icons/close.svg index 33f68f02bc..e6db01321a 100644 --- a/selfdrive/assets/icons/close.svg +++ b/selfdrive/assets/icons/close.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84c6b23bd3245954b86f80278511186fca4ddfa70d87c8b25e8f9fb76f9af758 -size 379 +oid sha256:a28a4dcaba33d800d109cc5f9a810065203d3bfccd104b2e503f6fe3fc5b6f91 +size 250 diff --git a/selfdrive/assets/icons/close2.svg b/selfdrive/assets/icons/close2.svg index 54a44146d7..56a36cecd5 100644 --- a/selfdrive/assets/icons/close2.svg +++ b/selfdrive/assets/icons/close2.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4eff44a05132ed9f99ad821993be4bab9b1a1c880e07441f3b887214bee62afe -size 825 +oid sha256:7d2a7cd3913ab97a386e946969f6a684895adf5d641c38dfd8f5efa0197a6c58 +size 513 diff --git a/selfdrive/assets/icons/couch.svg b/selfdrive/assets/icons/couch.svg index 7c58515200..f56970f289 100644 --- a/selfdrive/assets/icons/couch.svg +++ b/selfdrive/assets/icons/couch.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a428d2561198ebdc853ba6fb25a8b9c5a58064d000413694e1535adc06633c0a -size 2649 +oid sha256:dd5d8d3fce5e30662797f7eeed224f5775b2cabcc055f01f5ebf6e7b2657b616 +size 1801 diff --git a/selfdrive/assets/icons/disengage_on_accelerator.svg b/selfdrive/assets/icons/disengage_on_accelerator.svg index d5a8c87e21..eef5181935 100644 --- a/selfdrive/assets/icons/disengage_on_accelerator.svg +++ b/selfdrive/assets/icons/disengage_on_accelerator.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5252412f2225c89ea7e78ad0fbd6544170aea157693ce0f9778f26a64f582ec2 -size 6821 +oid sha256:baac01efc894527c8234b774c89cc57b69460d60ad81691a6d50ce0904c60ba7 +size 3638 diff --git a/selfdrive/assets/icons/experimental.svg b/selfdrive/assets/icons/experimental.svg index 3c31caa07e..8a97cdeac1 100644 --- a/selfdrive/assets/icons/experimental.svg +++ b/selfdrive/assets/icons/experimental.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c26afadff128244567a7cf98f1c998f97056b19209301ff7fc8851eb807fb748 -size 2193 +oid sha256:b7ab989c9fe22e7d2119bac5284cdccfba2889477d69f5f6c6b4470c7ee0aab3 +size 1801 diff --git a/selfdrive/assets/icons/experimental_grey.svg b/selfdrive/assets/icons/experimental_grey.svg index 8ba6c87bd5..25ab33f388 100644 --- a/selfdrive/assets/icons/experimental_grey.svg +++ b/selfdrive/assets/icons/experimental_grey.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ae28a53171567c8a0d52eec75cc49004cb8dd19dcab9a360784718ab8ec7c02 -size 1931 +oid sha256:90d441310f8e1833c661d8a4f0546aab2eb50eb21cc21beb0aff0d27b5ee6066 +size 1571 diff --git a/selfdrive/assets/icons/experimental_white.svg b/selfdrive/assets/icons/experimental_white.svg index 9714fe0c01..51d7698947 100644 --- a/selfdrive/assets/icons/experimental_white.svg +++ b/selfdrive/assets/icons/experimental_white.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99be696be983700d7eb1768bd1c840198a4eb9525b71e28efea49f13c358e519 -size 1891 +oid sha256:82648254da89edb0cf65ef63fc5e6e0741414051bfea8a40449132fd61b1ed0f +size 1533 diff --git a/selfdrive/assets/icons/eye_closed.svg b/selfdrive/assets/icons/eye_closed.svg index fcef6e8a3c..a9cb925186 100644 --- a/selfdrive/assets/icons/eye_closed.svg +++ b/selfdrive/assets/icons/eye_closed.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e0e5b451ed2e426fea99da24a9df6552be7048205b9aa7b89e46eeb89f6d08e -size 1490 +oid sha256:fcb0db3b77d6057b94544b6d91b6078fbd91b8f2de189322b90feae3d1ac29da +size 1054 diff --git a/selfdrive/assets/icons/eye_open.svg b/selfdrive/assets/icons/eye_open.svg index 7289c4a571..2befa13adb 100644 --- a/selfdrive/assets/icons/eye_open.svg +++ b/selfdrive/assets/icons/eye_open.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23935f9e2ddba8dafd4a5d0217f29783260a0832c9b0d3e6a2ef66d4529b91d2 -size 775 +oid sha256:411774f4a80831833a344e58932c23a6ebd1db73a6327a63259deca2c6f53613 +size 597 diff --git a/selfdrive/assets/icons/lock_closed.svg b/selfdrive/assets/icons/lock_closed.svg index 501d978a8a..22a510d1c2 100644 --- a/selfdrive/assets/icons/lock_closed.svg +++ b/selfdrive/assets/icons/lock_closed.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ef09792cc1893f81c64abd6b72091d3762c07b849863ce90508afdd29b392c02 -size 732 +oid sha256:64e8fc90b79c725a8bf5bbc99643989ae885570997092762e216738805649ade +size 492 diff --git a/selfdrive/assets/icons/triangle.svg b/selfdrive/assets/icons/triangle.svg index f3a8db44b7..233eb5e979 100644 --- a/selfdrive/assets/icons/triangle.svg +++ b/selfdrive/assets/icons/triangle.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aaf9a1967365f2c641bf7b54a409e32842cee0bfded7ebb01b1a95c4ac9f0154 -size 2163 +oid sha256:a83c9a78673429caf16d02917be4c8afedadff17b6ceb8c248703ad1120116ed +size 394 diff --git a/selfdrive/assets/icons/wifi_strength_full.svg b/selfdrive/assets/icons/wifi_strength_full.svg index c9d22d8961..4137d3cd4c 100644 --- a/selfdrive/assets/icons/wifi_strength_full.svg +++ b/selfdrive/assets/icons/wifi_strength_full.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:485dd0d4eb8968726003ae460bab4ff498127def52b8b1ed6d968f4629ab233a -size 1655 +oid sha256:3c16c005d666dea64ba6f85de50dfcf161353176eca3d9f475677cbc04fd0382 +size 1161 diff --git a/selfdrive/assets/icons/wifi_strength_high.svg b/selfdrive/assets/icons/wifi_strength_high.svg index ff001bd14a..17d721fe31 100644 --- a/selfdrive/assets/icons/wifi_strength_high.svg +++ b/selfdrive/assets/icons/wifi_strength_high.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c92fd8bebbe630f991fe3f61f61407f8002d29881698634239be3a6fae2fb4bc -size 1657 +oid sha256:0f207b9a1f4ab6009e261f03edd1d05a9d485217415664cba0253d32381e3a03 +size 1164 diff --git a/selfdrive/assets/icons/wifi_strength_low.svg b/selfdrive/assets/icons/wifi_strength_low.svg index bcc6e83c63..4088866370 100644 --- a/selfdrive/assets/icons/wifi_strength_low.svg +++ b/selfdrive/assets/icons/wifi_strength_low.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e629e3a84dc288278e455be4a8deff95730e24d9a0b894f8c417ac1c409670ab -size 1661 +oid sha256:6835937483e5a539618a94e3ec8fad661ae2f7afb39c958e6c8b007b8f4b9b2c +size 1198 diff --git a/selfdrive/assets/icons/wifi_strength_medium.svg b/selfdrive/assets/icons/wifi_strength_medium.svg index e14ebd238f..f0c029dedd 100644 --- a/selfdrive/assets/icons/wifi_strength_medium.svg +++ b/selfdrive/assets/icons/wifi_strength_medium.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52113c14656483854a814c50c93770f5e6ab7d63b2b5048ddd6fd47fdfd4a7de -size 1659 +oid sha256:7ebb1fb5539d9f8ea9899acf4f9cb359503dbe22cd38a3461ad43936348475b9 +size 1167 diff --git a/selfdrive/assets/icons/wifi_uploading.svg b/selfdrive/assets/icons/wifi_uploading.svg index b9392dfe7c..07a14a59f4 100644 --- a/selfdrive/assets/icons/wifi_uploading.svg +++ b/selfdrive/assets/icons/wifi_uploading.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bedd579f56c65fffe2ad571f92843b6d103826fe173f8f5b58fc2200d2ad8850 -size 1663 +oid sha256:e7cfefbda22b53f7eb72a25893ab41c00dabea49f690cafb1d224375a669e608 +size 1170 diff --git a/selfdrive/assets/images/button_continue_triangle.svg b/selfdrive/assets/images/button_continue_triangle.svg index ee9db369a4..e6d362927c 100644 --- a/selfdrive/assets/images/button_continue_triangle.svg +++ b/selfdrive/assets/images/button_continue_triangle.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1764c0c93703481b2ced63bc35c5a23a6c12388ca690ce4cf577b3b478a08b69 -size 197 +oid sha256:8084e5a8bbc16956a98b010092ae1c4e32b3391b0551fa1cd65cb1e2bb59d3df +size 169 diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh index a884d734d0..44afa369c1 100755 --- a/selfdrive/assets/prep-svg.sh +++ b/selfdrive/assets/prep-svg.sh @@ -1,13 +1,10 @@ #!/usr/bin/env bash set -e -# sudo apt install scour - -for svg in $(find icons/ -type f | grep svg$); do - # scour doesn't support overwriting input file - scour $svg --remove-metadata $svg.tmp - mv $svg.tmp $svg +for svg in $(find icons/ images/ -type f | grep svg$); do + bunx svgo $svg --multipass --pretty --indent 2 # convert to PNG + # sudo apt install inkscape convert -background none -resize 400% -density 384 $svg "${svg%.svg}.png" done From 83679bd856158b838fefb3070b8adbfdb7d8ebbe Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 01:33:16 +0100 Subject: [PATCH 067/142] selfdrive/assets: rasterize SVGs (#35243) * selfdrive/assets: rasterize SVGs * do entire assets dir * fixed size - 4096px is a lot * optimise them too * use inkscape * regenerate * abandon runtime svg --- selfdrive/assets/icons/checkmark.png | 3 +++ selfdrive/assets/icons/circled_check.png | 3 +++ selfdrive/assets/icons/circled_slash.png | 3 +++ selfdrive/assets/icons/close.png | 3 +++ selfdrive/assets/icons/close2.png | 3 +++ selfdrive/assets/icons/couch.png | 3 +++ .../assets/icons/disengage_on_accelerator.png | 3 +++ selfdrive/assets/icons/experimental.png | 3 +++ selfdrive/assets/icons/experimental_grey.png | 3 +++ selfdrive/assets/icons/experimental_white.png | 3 +++ selfdrive/assets/icons/eye_closed.png | 3 +++ selfdrive/assets/icons/eye_open.png | 3 +++ selfdrive/assets/icons/lock_closed.png | 3 +++ selfdrive/assets/icons/triangle.png | 3 +++ selfdrive/assets/icons/wifi_strength_full.png | 3 +++ selfdrive/assets/icons/wifi_strength_high.png | 3 +++ selfdrive/assets/icons/wifi_strength_low.png | 3 +++ .../assets/icons/wifi_strength_medium.png | 3 +++ selfdrive/assets/icons/wifi_uploading.png | 3 +++ .../images/button_continue_triangle.png | 3 +++ selfdrive/assets/prep-svg.sh | 19 ++++++++++++++++--- system/ui/lib/application.py | 9 +-------- 22 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 selfdrive/assets/icons/checkmark.png create mode 100644 selfdrive/assets/icons/circled_check.png create mode 100644 selfdrive/assets/icons/circled_slash.png create mode 100644 selfdrive/assets/icons/close.png create mode 100644 selfdrive/assets/icons/close2.png create mode 100644 selfdrive/assets/icons/couch.png create mode 100644 selfdrive/assets/icons/disengage_on_accelerator.png create mode 100644 selfdrive/assets/icons/experimental.png create mode 100644 selfdrive/assets/icons/experimental_grey.png create mode 100644 selfdrive/assets/icons/experimental_white.png create mode 100644 selfdrive/assets/icons/eye_closed.png create mode 100644 selfdrive/assets/icons/eye_open.png create mode 100644 selfdrive/assets/icons/lock_closed.png create mode 100644 selfdrive/assets/icons/triangle.png create mode 100644 selfdrive/assets/icons/wifi_strength_full.png create mode 100644 selfdrive/assets/icons/wifi_strength_high.png create mode 100644 selfdrive/assets/icons/wifi_strength_low.png create mode 100644 selfdrive/assets/icons/wifi_strength_medium.png create mode 100644 selfdrive/assets/icons/wifi_uploading.png create mode 100644 selfdrive/assets/images/button_continue_triangle.png diff --git a/selfdrive/assets/icons/checkmark.png b/selfdrive/assets/icons/checkmark.png new file mode 100644 index 0000000000..0f9a802a35 --- /dev/null +++ b/selfdrive/assets/icons/checkmark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc472f0e575e314c4006cb6e9845fb9fb9a13cf08fe74fbe1593dee53c20d977 +size 4329 diff --git a/selfdrive/assets/icons/circled_check.png b/selfdrive/assets/icons/circled_check.png new file mode 100644 index 0000000000..7611bc821f --- /dev/null +++ b/selfdrive/assets/icons/circled_check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdb0be280ac3a78bf95f5b92fafe94de5084ecc06836459c3a9fe1912a5b2454 +size 10479 diff --git a/selfdrive/assets/icons/circled_slash.png b/selfdrive/assets/icons/circled_slash.png new file mode 100644 index 0000000000..74a9b342f6 --- /dev/null +++ b/selfdrive/assets/icons/circled_slash.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2a992a83eaa87762e12dc226f36af48e1cdbfc3b83ef75b6a2fc4103e3697a0 +size 9120 diff --git a/selfdrive/assets/icons/close.png b/selfdrive/assets/icons/close.png new file mode 100644 index 0000000000..66d1545632 --- /dev/null +++ b/selfdrive/assets/icons/close.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c11f831c17080a8ffaa8469cf91a079a4abfb72e5238afe02b92bceb3442db0 +size 2656 diff --git a/selfdrive/assets/icons/close2.png b/selfdrive/assets/icons/close2.png new file mode 100644 index 0000000000..4497c97547 --- /dev/null +++ b/selfdrive/assets/icons/close2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbfed12ddb5731b7b568539fe59f382356b46fdd146a9e0a1b768d3e5efd0378 +size 4350 diff --git a/selfdrive/assets/icons/couch.png b/selfdrive/assets/icons/couch.png new file mode 100644 index 0000000000..677ad0f9ee --- /dev/null +++ b/selfdrive/assets/icons/couch.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:911f59f248015600da7ecc689398103d47dfc57f6be17ac8c8e543a726a6c64b +size 3311 diff --git a/selfdrive/assets/icons/disengage_on_accelerator.png b/selfdrive/assets/icons/disengage_on_accelerator.png new file mode 100644 index 0000000000..4134834448 --- /dev/null +++ b/selfdrive/assets/icons/disengage_on_accelerator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f23fabbf60fff6ef88ba6f27f2775b1ae6be172a994e41267983a9ec0f984bfc +size 15059 diff --git a/selfdrive/assets/icons/experimental.png b/selfdrive/assets/icons/experimental.png new file mode 100644 index 0000000000..2332fe11d0 --- /dev/null +++ b/selfdrive/assets/icons/experimental.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e58deb1778cf2826339f27e9f09eecc79ea137c1436210c14b71c352a649c77 +size 34953 diff --git a/selfdrive/assets/icons/experimental_grey.png b/selfdrive/assets/icons/experimental_grey.png new file mode 100644 index 0000000000..058c3e1358 --- /dev/null +++ b/selfdrive/assets/icons/experimental_grey.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f37a02dd914405c6f86f415700dd5985eb976b923e7abd6580d2da76533594e +size 9466 diff --git a/selfdrive/assets/icons/experimental_white.png b/selfdrive/assets/icons/experimental_white.png new file mode 100644 index 0000000000..6a9cfc4407 --- /dev/null +++ b/selfdrive/assets/icons/experimental_white.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b2dad33fead9f064c3a548651d6ef37daf82b6127c329683f538ec6e986ecbc +size 11204 diff --git a/selfdrive/assets/icons/eye_closed.png b/selfdrive/assets/icons/eye_closed.png new file mode 100644 index 0000000000..8eba02e600 --- /dev/null +++ b/selfdrive/assets/icons/eye_closed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64d9dc106172d3a54088ef51a27a48145154ca040c43ecbc8d626fa42e38886e +size 9352 diff --git a/selfdrive/assets/icons/eye_open.png b/selfdrive/assets/icons/eye_open.png new file mode 100644 index 0000000000..9783716ff3 --- /dev/null +++ b/selfdrive/assets/icons/eye_open.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94246db66e774cbaee618931f76ecc38ecb72eca097d1e6c20a8dec2a5f8cd29 +size 7087 diff --git a/selfdrive/assets/icons/lock_closed.png b/selfdrive/assets/icons/lock_closed.png new file mode 100644 index 0000000000..21e6795737 --- /dev/null +++ b/selfdrive/assets/icons/lock_closed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b89b8803bb610515aef051c93b833dc62f8c847558873cfd50a0b240c968449 +size 4911 diff --git a/selfdrive/assets/icons/triangle.png b/selfdrive/assets/icons/triangle.png new file mode 100644 index 0000000000..47ff24f200 --- /dev/null +++ b/selfdrive/assets/icons/triangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f2745ce89c926e507888ea7c6df1884aab045887048cf0d813407396a2e6b18 +size 5894 diff --git a/selfdrive/assets/icons/wifi_strength_full.png b/selfdrive/assets/icons/wifi_strength_full.png new file mode 100644 index 0000000000..1a710f2967 --- /dev/null +++ b/selfdrive/assets/icons/wifi_strength_full.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b7f0971cf612b905ccb338e40921932773538517fc9f0f7a4a847ad596287a9 +size 7171 diff --git a/selfdrive/assets/icons/wifi_strength_high.png b/selfdrive/assets/icons/wifi_strength_high.png new file mode 100644 index 0000000000..2d360968b6 --- /dev/null +++ b/selfdrive/assets/icons/wifi_strength_high.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2afacf302bcdc3f5e0d2f734508e8fea6f803813098d52fa6b878c765ba7a58 +size 9360 diff --git a/selfdrive/assets/icons/wifi_strength_low.png b/selfdrive/assets/icons/wifi_strength_low.png new file mode 100644 index 0000000000..d016528304 --- /dev/null +++ b/selfdrive/assets/icons/wifi_strength_low.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7dfe50815c76d459dc104ce4a5e4b5dfd882e11b65e07706cacd336e69e788f4 +size 9756 diff --git a/selfdrive/assets/icons/wifi_strength_medium.png b/selfdrive/assets/icons/wifi_strength_medium.png new file mode 100644 index 0000000000..9c943543a4 --- /dev/null +++ b/selfdrive/assets/icons/wifi_strength_medium.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:304ab97ac9724a7a133a36c2d14da9fe8ab660c9a29abb99cc0a4828f94d8801 +size 9627 diff --git a/selfdrive/assets/icons/wifi_uploading.png b/selfdrive/assets/icons/wifi_uploading.png new file mode 100644 index 0000000000..19d9bf36ce --- /dev/null +++ b/selfdrive/assets/icons/wifi_uploading.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8eac201013322db1580649a253da7ae38eb9f16f6089234e769b746951e874ee +size 7171 diff --git a/selfdrive/assets/images/button_continue_triangle.png b/selfdrive/assets/images/button_continue_triangle.png new file mode 100644 index 0000000000..c56e112094 --- /dev/null +++ b/selfdrive/assets/images/button_continue_triangle.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9218e02c42b0f80858477255e24a97da4cf0b2898fc76f3806409d65b104668 +size 4510 diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh index 44afa369c1..661464a5c6 100755 --- a/selfdrive/assets/prep-svg.sh +++ b/selfdrive/assets/prep-svg.sh @@ -1,10 +1,23 @@ #!/usr/bin/env bash set -e -for svg in $(find icons/ images/ -type f | grep svg$); do +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +# sudo apt install inkscape + +for svg in $(find $DIR -type f | grep svg$); do bunx svgo $svg --multipass --pretty --indent 2 # convert to PNG - # sudo apt install inkscape - convert -background none -resize 400% -density 384 $svg "${svg%.svg}.png" + png="${svg%.svg}.png" + width=$(inkscape --query-width "$svg") + height=$(inkscape --query-height "$svg") + if (( $(echo "$width > $height" | bc -l) )); then + export_dim="--export-width=512" + else + export_dim="--export-height=512" + fi + inkscape "$svg" --export-filename="$png" $export_dim + + optipng -o7 -strip all "$png" done diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index fb7f7832a4..8961bb0327 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -80,10 +80,7 @@ class GuiApplication: def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply = False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" - if image_path.endswith('.svg'): - image = self._load_image_from_svg(image_path) - else: - image = rl.load_image(image_path) + image = rl.load_image(image_path) if alpha_premultiply: rl.image_alpha_premultiply(image) @@ -112,10 +109,6 @@ class GuiApplication: rl.unload_image(image) return texture - def _load_image_from_svg(self, svg_path: str): - # TODO: Implement SVG loading - assert(0) - def close(self): if not rl.is_window_ready(): return From 512d83cc36e7ade7d9623c81149df945f8764055 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 12:41:20 +0100 Subject: [PATCH 068/142] ui(raylib): setup.py (#35140) * setup.py * better font * use gui_button * btn * fix button and triangle * low voltage text color * fix network page * HARDWARE.get_os_version() * typing * white title * update default text color * use default font color * fix software screen * fix software screen * radio font size * line length * fix regex * draw svgs * comment is out of date * add cairosvg * use cairosvg * remove unused import * support other image types * revert origin * fix setup warning icon * fix * remove cairosvg * use pngs * wrap * fix disabled style * TODO * revert uv.lock * use new file paths (not rasterized yet) * oops * fixes * params not used * network check thread * oops * fix custom URL and download failed screens * clear keyboard * rm * fixes * show full error message * check network type --- system/ui/lib/application.py | 2 +- system/ui/setup.py | 344 +++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 1 deletion(-) create mode 100755 system/ui/setup.py diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8961bb0327..9e6c8ef28d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -17,7 +17,7 @@ DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' DEFAULT_TEXT_SIZE = 60 -DEFAULT_TEXT_COLOR = rl.Color(200, 200, 200, 255) +DEFAULT_TEXT_COLOR = rl.WHITE ASSETS_DIR = os.path.join(BASEDIR, "selfdrive/assets") FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts") diff --git a/system/ui/setup.py b/system/ui/setup.py new file mode 100755 index 0000000000..8c75b8e7c7 --- /dev/null +++ b/system/ui/setup.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 +import os +import re +import threading +import time +import urllib.request +from enum import IntEnum +import pyray as rl + +from cereal import log +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.button import gui_button, ButtonStyle +from openpilot.system.ui.lib.label import gui_label, gui_text_box +from openpilot.system.ui.widgets.network import WifiManagerUI, WifiManagerWrapper +from openpilot.system.ui.widgets.keyboard import Keyboard + +NetworkType = log.DeviceState.NetworkType + +MARGIN = 50 +TITLE_FONT_SIZE = 116 +TITLE_FONT_WEIGHT = FontWeight.MEDIUM +NEXT_BUTTON_WIDTH = 310 +BODY_FONT_SIZE = 96 +BUTTON_HEIGHT = 160 +BUTTON_SPACING = 50 + +OPENPILOT_URL = "https://openpilot.comma.ai" +USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" + + +class SetupState(IntEnum): + LOW_VOLTAGE = 0 + GETTING_STARTED = 1 + NETWORK_SETUP = 2 + SOFTWARE_SELECTION = 3 + CUSTOM_URL = 4 + DOWNLOADING = 5 + DOWNLOAD_FAILED = 6 + + +class Setup: + def __init__(self): + self.state = SetupState.GETTING_STARTED + self.network_check_thread = None + self.network_connected = threading.Event() + self.wifi_connected = threading.Event() + self.stop_network_check_thread = threading.Event() + self.failed_url = "" + self.failed_reason = "" + self.download_url = "" + self.download_progress = 0 + self.download_thread = None + self.wifi_manager = WifiManagerWrapper() + self.wifi_ui = WifiManagerUI(self.wifi_manager) + self.keyboard = Keyboard() + self.selected_radio = None + + self.warning = gui_app.texture("icons/warning.png", 150, 150) + self.checkmark = gui_app.texture("icons/circled_check.png", 100, 100) + + try: + with open("/sys/class/hwmon/hwmon1/in1_input") as f: + voltage = float(f.read().strip()) / 1000.0 + if voltage < 7: + self.state = SetupState.LOW_VOLTAGE + except (FileNotFoundError, ValueError): + self.state = SetupState.LOW_VOLTAGE + + def render(self, rect: rl.Rectangle): + if self.state == SetupState.LOW_VOLTAGE: + self.render_low_voltage(rect) + elif self.state == SetupState.GETTING_STARTED: + self.render_getting_started(rect) + elif self.state == SetupState.NETWORK_SETUP: + self.render_network_setup(rect) + elif self.state == SetupState.SOFTWARE_SELECTION: + self.render_software_selection(rect) + elif self.state == SetupState.CUSTOM_URL: + self.render_custom_url() + elif self.state == SetupState.DOWNLOADING: + self.render_downloading(rect) + elif self.state == SetupState.DOWNLOAD_FAILED: + self.render_download_failed(rect) + + def render_low_voltage(self, rect: rl.Rectangle): + rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) + + title_rect = rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE) + gui_label(title_rect, "WARNING: Low Voltage", TITLE_FONT_SIZE, rl.Color(255, 89, 79, 255), FontWeight.MEDIUM) + + body_rect = rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100 + TITLE_FONT_SIZE + 25, rect.width - 500 - 150, BODY_FONT_SIZE * 3) + gui_text_box(body_rect, "Power your device in a car with a harness or proceed at your own risk.", BODY_FONT_SIZE) + + button_width = (rect.width - MARGIN * 3) / 2 + button_y = rect.height - MARGIN - BUTTON_HEIGHT + + if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Power off"): + HARDWARE.shutdown() + + if gui_button(rl.Rectangle(rect.x + MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT), "Continue"): + self.state = SetupState.GETTING_STARTED + + def render_getting_started(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(rect.x + 165, rect.y + 280, rect.width - 265, TITLE_FONT_SIZE) + gui_label(title_rect, "Getting Started", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM) + + desc_rect = rl.Rectangle(rect.x + 165, rect.y + 280 + TITLE_FONT_SIZE + 90, rect.width - 500, BODY_FONT_SIZE * 3) + gui_text_box(desc_rect, "Before we get on the road, let's finish installation and cover some details.", BODY_FONT_SIZE) + + btn_rect = rl.Rectangle(rect.width - NEXT_BUTTON_WIDTH, 0, NEXT_BUTTON_WIDTH, rect.height) + + ret = gui_button(btn_rect, "", button_style=ButtonStyle.PRIMARY, border_radius=0) + triangle = gui_app.texture("images/button_continue_triangle.png", 54, int(btn_rect.height)) + rl.draw_texture_v(triangle, rl.Vector2(btn_rect.x + btn_rect.width / 2 - triangle.width / 2, btn_rect.height / 2 - triangle.height / 2), rl.WHITE) + + if ret: + self.state = SetupState.NETWORK_SETUP + self.wifi_manager.request_scan() + self.start_network_check() + + def check_network_connectivity(self): + while not self.stop_network_check_thread.is_set(): + if self.state == SetupState.NETWORK_SETUP: + try: + urllib.request.urlopen("https://google.com", timeout=2) + self.network_connected.set() + if HARDWARE.get_network_type() == NetworkType.wifi: + self.wifi_connected.set() + else: + self.wifi_connected.clear() + except Exception: + self.network_connected.clear() + time.sleep(1) + + def start_network_check(self): + if self.network_check_thread is None or not self.network_check_thread.is_alive(): + self.network_check_thread = threading.Thread(target=self.check_network_connectivity, daemon=True) + self.network_check_thread.start() + + def close(self): + if self.network_check_thread is not None: + self.stop_network_check_thread.set() + self.network_check_thread.join() + + def render_network_setup(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE) + gui_label(title_rect, "Connect to Wi-Fi", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM) + + wifi_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN + 25, rect.width - MARGIN * 2, + rect.height - TITLE_FONT_SIZE - 25 - BUTTON_HEIGHT - MARGIN * 3) + rl.draw_rectangle_rounded(wifi_rect, 0.05, 10, rl.Color(51, 51, 51, 255)) + wifi_content_rect = rl.Rectangle(wifi_rect.x + MARGIN, wifi_rect.y, wifi_rect.width - MARGIN * 2, wifi_rect.height) + self.wifi_ui.render(wifi_content_rect) + + button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 + button_y = rect.height - BUTTON_HEIGHT - MARGIN + + if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Back"): + self.state = SetupState.GETTING_STARTED + + # Check network connectivity status + continue_enabled = self.network_connected.is_set() + continue_text = ("Continue" if self.wifi_connected.is_set() else "Continue without Wi-Fi") if continue_enabled else "Waiting for internet" + + if gui_button( + rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT), + continue_text, + button_style=ButtonStyle.PRIMARY if continue_enabled else ButtonStyle.NORMAL, + ): + self.state = SetupState.SOFTWARE_SELECTION + self.stop_network_check_thread.set() + + def render_software_selection(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE) + gui_label(title_rect, "Choose Software to Install", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM) + + radio_height = 230 + radio_spacing = 30 + + openpilot_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN * 2, rect.width - MARGIN * 2, radio_height) + openpilot_selected = self.selected_radio == "openpilot" + + rl.draw_rectangle_rounded(openpilot_rect, 0.1, 10, rl.Color(70, 91, 234, 255) if openpilot_selected else rl.Color(79, 79, 79, 255)) + gui_label(rl.Rectangle(openpilot_rect.x + 100, openpilot_rect.y, openpilot_rect.width - 200, radio_height), "openpilot", BODY_FONT_SIZE) + + if openpilot_selected: + checkmark_pos = rl.Vector2(openpilot_rect.x + openpilot_rect.width - 100 - self.checkmark.width, + openpilot_rect.y + radio_height / 2 - self.checkmark.height / 2) + rl.draw_texture_v(self.checkmark, checkmark_pos, rl.WHITE) + + custom_rect = rl.Rectangle(rect.x + MARGIN, rect.y + TITLE_FONT_SIZE + MARGIN * 2 + radio_height + radio_spacing, rect.width - MARGIN * 2, radio_height) + custom_selected = self.selected_radio == "custom" + + rl.draw_rectangle_rounded(custom_rect, 0.1, 10, rl.Color(70, 91, 234, 255) if custom_selected else rl.Color(79, 79, 79, 255)) + gui_label(rl.Rectangle(custom_rect.x + 100, custom_rect.y, custom_rect.width - 200, radio_height), "Custom Software", BODY_FONT_SIZE) + + if custom_selected: + checkmark_pos = rl.Vector2(custom_rect.x + custom_rect.width - 100 - self.checkmark.width, custom_rect.y + radio_height / 2 - self.checkmark.height / 2) + rl.draw_texture_v(self.checkmark, checkmark_pos, rl.WHITE) + + mouse_pos = rl.get_mouse_position() + if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): + if rl.check_collision_point_rec(mouse_pos, openpilot_rect): + self.selected_radio = "openpilot" + elif rl.check_collision_point_rec(mouse_pos, custom_rect): + self.selected_radio = "custom" + + button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 + button_y = rect.height - BUTTON_HEIGHT - MARGIN + + if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Back"): + self.state = SetupState.NETWORK_SETUP + + continue_enabled = self.selected_radio is not None + if gui_button( + rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT), + "Continue", + button_style=ButtonStyle.PRIMARY, + is_enabled=continue_enabled, + ): + if continue_enabled: + if self.selected_radio == "openpilot": + self.download(OPENPILOT_URL) + else: + self.state = SetupState.CUSTOM_URL + + def render_downloading(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(rect.x, rect.y + rect.height / 2 - TITLE_FONT_SIZE / 2, rect.width, TITLE_FONT_SIZE) + gui_label(title_rect, "Downloading...", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + + def render_download_failed(self, rect: rl.Rectangle): + title_rect = rl.Rectangle(rect.x + 117, rect.y + 185, rect.width - 117, TITLE_FONT_SIZE) + gui_label(title_rect, "Download Failed", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM) + + url_rect = rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE + 67, rect.width - 117 - 100, 64) + gui_label(url_rect, self.failed_url, 64, font_weight=FontWeight.NORMAL) + + error_rect = rl.Rectangle(rect.x + 117, rect.y + 185 + TITLE_FONT_SIZE + 67 + 64 + 48, + rect.width - 117 - 100, rect.height - 185 + TITLE_FONT_SIZE + 67 + 64 + 48 - BUTTON_HEIGHT - MARGIN * 2) + gui_text_box(error_rect, self.failed_reason, BODY_FONT_SIZE) + + button_width = (rect.width - BUTTON_SPACING - MARGIN * 2) / 2 + button_y = rect.height - BUTTON_HEIGHT - MARGIN + + if gui_button(rl.Rectangle(rect.x + MARGIN, button_y, button_width, BUTTON_HEIGHT), "Reboot device"): + HARDWARE.reboot() + + if gui_button(rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT), "Start over", + button_style=ButtonStyle.PRIMARY): + self.state = SetupState.GETTING_STARTED + + def render_custom_url(self): + result = self.keyboard.render("Enter URL", "for Custom Software") + + # Enter pressed + if result == 1: + url = self.keyboard.text + self.keyboard.clear() + if url: + self.download(url) + + # Cancel pressed + elif result == 0: + self.state = SetupState.SOFTWARE_SELECTION + + def download(self, url: str): + # autocomplete incomplete URLs + if re.match("^([^/.]+)/([^/]+)$", url): + url = f"https://installer.comma.ai/{url}" + + self.download_url = url + self.state = SetupState.DOWNLOADING + + self.download_thread = threading.Thread(target=self._download_thread, daemon=True) + self.download_thread.start() + + def _download_thread(self): + try: + import tempfile + + _, tmpfile = tempfile.mkstemp(prefix="installer_") + + headers = {"User-Agent": USER_AGENT, "X-openpilot-serial": HARDWARE.get_serial()} + req = urllib.request.Request(self.download_url, headers=headers) + + with open(tmpfile, 'wb') as f, urllib.request.urlopen(req, timeout=30) as response: + total_size = int(response.headers.get('content-length', 0)) + downloaded = 0 + block_size = 8192 + + while True: + buffer = response.read(block_size) + if not buffer: + break + + downloaded += len(buffer) + f.write(buffer) + + if total_size: + self.download_progress = int(downloaded * 100 / total_size) + + is_elf = False + with open(tmpfile, 'rb') as f: + header = f.read(4) + is_elf = header == b'\x7fELF' + + if not is_elf: + self.download_failed(self.download_url, "No custom software found at this URL.") + return + + os.rename(tmpfile, "/tmp/installer") + os.chmod("/tmp/installer", 0o755) + + with open("/tmp/installer_url", "w") as f: + f.write(self.download_url) + + gui_app.request_close() + + except Exception: + error_msg = "Ensure the entered URL is valid, and the device's internet connection is good." + self.download_failed(self.download_url, error_msg) + + def download_failed(self, url: str, reason: str): + self.failed_url = url + self.failed_reason = reason + self.state = SetupState.DOWNLOAD_FAILED + + +def main(): + try: + gui_app.init_window("Setup") + setup = Setup() + for _ in gui_app.render(): + setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + setup.close() + except Exception as e: + print(f"Setup error: {e}") + finally: + gui_app.close() + + +if __name__ == "__main__": + main() From a6456503b092f636e4b9e9d730c297397c2a46b4 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 13:38:51 +0100 Subject: [PATCH 069/142] selfdrive/assets: add keyboard icons (#35247) --- selfdrive/assets/icons/arrow_right.png | 3 +++ selfdrive/assets/icons/arrow_right.svg | 3 +++ selfdrive/assets/icons/backspace.png | 3 +++ selfdrive/assets/icons/backspace.svg | 3 +++ selfdrive/assets/icons/shift.png | 3 +++ selfdrive/assets/icons/shift.svg | 3 +++ selfdrive/assets/icons/shift_lock.png | 3 +++ selfdrive/assets/icons/shift_lock.svg | 3 +++ 8 files changed, 24 insertions(+) create mode 100644 selfdrive/assets/icons/arrow_right.png create mode 100644 selfdrive/assets/icons/arrow_right.svg create mode 100644 selfdrive/assets/icons/backspace.png create mode 100644 selfdrive/assets/icons/backspace.svg create mode 100644 selfdrive/assets/icons/shift.png create mode 100644 selfdrive/assets/icons/shift.svg create mode 100644 selfdrive/assets/icons/shift_lock.png create mode 100644 selfdrive/assets/icons/shift_lock.svg diff --git a/selfdrive/assets/icons/arrow_right.png b/selfdrive/assets/icons/arrow_right.png new file mode 100644 index 0000000000..9897695399 --- /dev/null +++ b/selfdrive/assets/icons/arrow_right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edfbafa7fe485b20d26382f198856c3ea798e57826aa8c4553c49a35aee005ed +size 883 diff --git a/selfdrive/assets/icons/arrow_right.svg b/selfdrive/assets/icons/arrow_right.svg new file mode 100644 index 0000000000..c2df1310a2 --- /dev/null +++ b/selfdrive/assets/icons/arrow_right.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb66f3b15fc670cb9ea2b17fc75984cc3fc4f0118b880ce555d3cc7672b72fa5 +size 583 diff --git a/selfdrive/assets/icons/backspace.png b/selfdrive/assets/icons/backspace.png new file mode 100644 index 0000000000..ece10a3d73 --- /dev/null +++ b/selfdrive/assets/icons/backspace.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36e16def9e727216e491cafbb3fe655f82797389b37d63946f1a9c030ecc6f96 +size 4470 diff --git a/selfdrive/assets/icons/backspace.svg b/selfdrive/assets/icons/backspace.svg new file mode 100644 index 0000000000..8da38a106a --- /dev/null +++ b/selfdrive/assets/icons/backspace.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10d92681ea39067298851a1d298959fbd1904e63431fbbcfff953f22a65b61c3 +size 468 diff --git a/selfdrive/assets/icons/shift.png b/selfdrive/assets/icons/shift.png new file mode 100644 index 0000000000..7026a807e7 --- /dev/null +++ b/selfdrive/assets/icons/shift.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25abdab506c6ae81b0a379d86378e0ad73f6fa093be5dc81ed1f192e5b146044 +size 3113 diff --git a/selfdrive/assets/icons/shift.svg b/selfdrive/assets/icons/shift.svg new file mode 100644 index 0000000000..7f890856bd --- /dev/null +++ b/selfdrive/assets/icons/shift.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30f7cd752143d0d9148263d479d5c38809c9ecd64a0bfb5f0fe6d393faedcaa7 +size 232 diff --git a/selfdrive/assets/icons/shift_lock.png b/selfdrive/assets/icons/shift_lock.png new file mode 100644 index 0000000000..1a0464ebcd --- /dev/null +++ b/selfdrive/assets/icons/shift_lock.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0796cb759559f1608a7498c2a00ca48e4d398e4a7b927778db0ae3f00b5c5bcb +size 3092 diff --git a/selfdrive/assets/icons/shift_lock.svg b/selfdrive/assets/icons/shift_lock.svg new file mode 100644 index 0000000000..388e722b49 --- /dev/null +++ b/selfdrive/assets/icons/shift_lock.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9eeae279ca4022f109e8a1873def65edfc86108c5315e0af98e7f413db75218 +size 1074 From dcfbb99b1358582aab63e82c903d3229008e4673 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 16 May 2025 20:50:27 +0800 Subject: [PATCH 070/142] system/ui: add password visibility toggle with eye icon to keyboard (#35246) * add password visibility toggle with eye icon to keyboard * added show_password_toggle --- system/ui/lib/inputbox.py | 7 +++-- system/ui/widgets/keyboard.py | 51 ++++++++++++++++++++++++++++++----- system/ui/widgets/network.py | 2 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index d933d42476..8e53446b43 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -62,13 +62,12 @@ class InputBox: return True return False - def render(self, rect, color=rl.LIGHTGRAY, border_color=rl.DARKGRAY, text_color=rl.BLACK, font_size=80): + def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80): # Handle mouse input self._handle_mouse_input(rect, font_size) # Draw input box rl.draw_rectangle_rec(rect, color) - rl.draw_rectangle_lines_ex(rect, 1, border_color) # Process keyboard input self._handle_keyboard_input() @@ -81,7 +80,7 @@ class InputBox: # Display text font = gui_app.font() - display_text = "•" * len(self._input_text) if self._password_mode else self._input_text + display_text = "*" * len(self._input_text) if self._password_mode else self._input_text padding = 10 rl.draw_text_ex( font, @@ -100,7 +99,7 @@ class InputBox: cursor_height = font_size + 4 cursor_y = rect.y + rect.height / 2 - cursor_height / 2 - rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.BLACK) + rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.LIGHTGRAY) def _handle_mouse_input(self, rect, font_size): """Handle mouse clicks to position cursor.""" diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index b626a26b67..37580f80e5 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,5 +1,5 @@ import pyray as rl -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.button import gui_button from openpilot.system.ui.lib.inputbox import InputBox from openpilot.system.ui.lib.label import gui_label @@ -45,11 +45,16 @@ keyboard_layouts = { class Keyboard: - def __init__(self, max_text_size: int = 255, min_text_size: int = 0): + def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False): self._layout = keyboard_layouts["lowercase"] self._max_text_size = max_text_size self._min_text_size = min_text_size self._input_box = InputBox(max_text_size) + self._password_mode = password_mode + self._show_password_toggle = show_password_toggle + + self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54) + self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) @property def text(self): @@ -60,14 +65,17 @@ class Keyboard: def render(self, title: str, sub_title: str): rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN) - gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) - gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) - if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"): + gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90, font_weight=FontWeight.BOLD) + gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, font_weight=FontWeight.NORMAL) + if gui_button(rl.Rectangle(rect.x + rect.width - 386, rect.y, 386, 125), "Cancel"): self.clear() return 0 - # Text box for input - self._input_box.render(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100)) + # Draw input box and password toggle + input_margin = 25 + input_box_rect = rl.Rectangle(rect.x + input_margin, rect.y + 160, rect.width - input_margin, 100) + self._render_input_area(input_box_rect) + h_space, v_space = 15, 15 row_y_start = rect.y + 300 # Starting Y position for the first row key_height = (rect.height - 300 - 3 * v_space) / 4 @@ -95,6 +103,35 @@ class Keyboard: return -1 + def _render_input_area(self, input_rect: rl.Rectangle): + if self._show_password_toggle: + self._input_box.set_password_mode(self._password_mode) + self._input_box.render(rl.Rectangle(input_rect.x, input_rect.y, input_rect.width - 100, input_rect.height)) + + # render eye icon + eye_texture = self._eye_closed_texture if self._password_mode else self._eye_open_texture + + eye_rect = rl.Rectangle(input_rect.x + input_rect.width - 90, input_rect.y, 80, input_rect.height) + eye_x = eye_rect.x + (eye_rect.width - eye_texture.width) / 2 + eye_y = eye_rect.y + (eye_rect.height - eye_texture.height) / 2 + + rl.draw_texture_v(eye_texture, rl.Vector2(eye_x, eye_y), rl.WHITE) + + # Handle click on eye icon + if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec( + rl.get_mouse_position(), eye_rect + ): + self._password_mode = not self._password_mode + else: + self._input_box.render(input_rect) + + rl.draw_line_ex( + rl.Vector2(input_rect.x, input_rect.y + input_rect.height - 2), + rl.Vector2(input_rect.x + input_rect.width, input_rect.y + input_rect.height - 2), + 3.0, # 3 pixel thickness + rl.Color(189, 189, 189, 255), + ) + def handle_key_press(self, key): if key in (SHIFT_DOWN_KEY, ABC_KEY): self._layout = keyboard_layouts["lowercase"] diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index d34ad44f55..5b9d05c96e 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -48,7 +48,7 @@ class WifiManagerUI: self.state: UIState = StateIdle() self.btn_width = 200 self.scroll_panel = GuiScrollPanel() - self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH) + self.keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True) self._networks: list[NetworkInfo] = [] From b740d23dd02de3e314d6b4e4c7a6803ceddaba13 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 16 May 2025 20:51:08 +0800 Subject: [PATCH 071/142] system/ui: display icons in wifi manager (#35244) display icons in wifi manager --- system/ui/widgets/network.py | 66 ++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 5b9d05c96e..29d9a499da 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -2,11 +2,11 @@ from dataclasses import dataclass from typing import Literal import pyray as rl -from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.button import gui_button from openpilot.system.ui.lib.label import gui_label from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel +from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper, SecurityType from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.confirm_dialog import confirm_dialog @@ -14,7 +14,14 @@ NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 MAX_PASSWORD_LENGTH = 64 ITEM_HEIGHT = 160 +ICON_SIZE = 49 +STRENGTH_ICONS = [ + "icons/wifi_strength_low.png", + "icons/wifi_strength_medium.png", + "icons/wifi_strength_high.png", + "icons/wifi_strength_full.png", +] @dataclass class StateIdle: @@ -109,14 +116,14 @@ class WifiManagerUI: rl.end_scissor_mode() def _draw_network_item(self, rect, network: NetworkInfo, clicked: bool): - label_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT) - state_rect = rl.Rectangle(rect.x + rect.width - self.btn_width * 2 - 150, rect.y, 300, ITEM_HEIGHT) + spacing = 50 + ssid_rect = rl.Rectangle(rect.x, rect.y, rect.width - self.btn_width * 2, ITEM_HEIGHT) + signal_icon_rect = rl.Rectangle(rect.x + rect.width - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE) + security_icon_rect = rl.Rectangle(signal_icon_rect.x - spacing - ICON_SIZE, rect.y + (ITEM_HEIGHT - ICON_SIZE) / 2, ICON_SIZE, ICON_SIZE) - gui_label(label_rect, network.ssid, 55) + gui_label(ssid_rect, network.ssid, 55) status_text = "" - if network.is_connected: - status_text = "Connected" match self.state: case StateConnecting(network=connecting): if connecting.ssid == network.ssid: @@ -124,26 +131,49 @@ class WifiManagerUI: case StateForgetting(network=forgetting): if forgetting.ssid == network.ssid: status_text = "FORGETTING..." + if status_text: - rl.gui_label(state_rect, status_text) + status_text_rect = rl.Rectangle(security_icon_rect.x - 410, rect.y, 410, ITEM_HEIGHT) + rl.gui_label(status_text_rect, status_text) + else: + # If the network is saved, show the "Forget" button + if network.is_saved: + forget_btn_rect = rl.Rectangle(security_icon_rect.x - self.btn_width - spacing, + rect.y + (ITEM_HEIGHT - 80) / 2, + self.btn_width, + 80, + ) + if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget") and clicked: + self.state = StateShowForgetConfirm(network) - # If the network is saved, show the "Forget" button - if network.is_saved: - forget_btn_rect = rl.Rectangle( - rect.x + rect.width - self.btn_width, - rect.y + (ITEM_HEIGHT - 80) / 2, - self.btn_width, - 80, - ) - if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget") and clicked: - self.state = StateShowForgetConfirm(network) + self._draw_status_icon(security_icon_rect, network) + self._draw_signal_strength_icon(signal_icon_rect, network) - if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), label_rect) and clicked: + if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), ssid_rect) and clicked: if not network.is_saved: self.state = StateNeedsAuth(network) else: self.connect_to_network(network) + def _draw_status_icon(self, rect, network: NetworkInfo): + """Draw the status icon based on network's connection state""" + icon_file = "" + if network.is_connected: + icon_file = "icons/checkmark.png" + elif network.security_type == SecurityType.UNSUPPORTED: + icon_file = "icons/circled_slash.png" + else: + icon_file = "icons/lock_closed.png" + + texture = gui_app.texture(icon_file, ICON_SIZE, ICON_SIZE) + icon_rect = rl.Vector2(rect.x, rect.y + (ICON_SIZE - texture.height) / 2) + rl.draw_texture_v(texture, icon_rect, rl.WHITE) + + def _draw_signal_strength_icon(self, rect: rl.Rectangle, network: NetworkInfo): + """Draw the Wi-Fi signal strength icon based on network's signal strength""" + strength_level = max(0, min(3, round(network.strength / 33.0))) + rl.draw_texture_v(gui_app.texture(STRENGTH_ICONS[strength_level], ICON_SIZE, ICON_SIZE), rl.Vector2(rect.x, rect.y), rl.WHITE) + def connect_to_network(self, network: NetworkInfo, password=''): self.state = StateConnecting(network) if network.is_saved and not password: From 6adc7e6bdb318a178595a6532a21da8d79a97cb0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 17 May 2025 02:10:58 +0800 Subject: [PATCH 072/142] system/ui: disable continue button when no network connecton in setup.py (#35249) disable continue button when no network connecton --- system/ui/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/setup.py b/system/ui/setup.py index 8c75b8e7c7..7cf4cac9b4 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -167,6 +167,7 @@ class Setup: rl.Rectangle(rect.x + MARGIN + button_width + BUTTON_SPACING, button_y, button_width, BUTTON_HEIGHT), continue_text, button_style=ButtonStyle.PRIMARY if continue_enabled else ButtonStyle.NORMAL, + is_enabled=continue_enabled, ): self.state = SetupState.SOFTWARE_SELECTION self.stop_network_check_thread.set() From e9680a40bf2186545f05d9431d9ac440f92329f7 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 19:17:24 +0100 Subject: [PATCH 073/142] selfdrive/assets: generate keyboard icons from bootstrap-icons (#35250) * Revert "selfdrive/assets: add keyboard icons (#35247)" This reverts commit a6456503b092f636e4b9e9d730c297397c2a46b4. * selfdrive/assets: use bootstrap icons, generate keyboard icons * cleanup * switch shift-fill for capslock --- selfdrive/assets/icons/arrow-down.png | 3 +++ selfdrive/assets/icons/arrow-right.png | 3 +++ selfdrive/assets/icons/arrow_right.png | 3 --- selfdrive/assets/icons/arrow_right.svg | 3 --- selfdrive/assets/icons/backspace.png | 4 ++-- selfdrive/assets/icons/backspace.svg | 3 --- selfdrive/assets/icons/capslock.png | 3 +++ selfdrive/assets/icons/shift.png | 4 ++-- selfdrive/assets/icons/shift.svg | 3 --- selfdrive/assets/icons/shift_lock.png | 3 --- selfdrive/assets/icons/shift_lock.svg | 3 --- selfdrive/assets/prep-svg.sh | 25 ++++++++++++++++++++++++- 12 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 selfdrive/assets/icons/arrow-down.png create mode 100644 selfdrive/assets/icons/arrow-right.png delete mode 100644 selfdrive/assets/icons/arrow_right.png delete mode 100644 selfdrive/assets/icons/arrow_right.svg delete mode 100644 selfdrive/assets/icons/backspace.svg create mode 100644 selfdrive/assets/icons/capslock.png delete mode 100644 selfdrive/assets/icons/shift.svg delete mode 100644 selfdrive/assets/icons/shift_lock.png delete mode 100644 selfdrive/assets/icons/shift_lock.svg diff --git a/selfdrive/assets/icons/arrow-down.png b/selfdrive/assets/icons/arrow-down.png new file mode 100644 index 0000000000..834c1cb8b9 --- /dev/null +++ b/selfdrive/assets/icons/arrow-down.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e12a3cf36fdef107d237457e20b44e16320414f9de8a1791aff1ec5fd85ccef +size 2390 diff --git a/selfdrive/assets/icons/arrow-right.png b/selfdrive/assets/icons/arrow-right.png new file mode 100644 index 0000000000..b275134f64 --- /dev/null +++ b/selfdrive/assets/icons/arrow-right.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a999d5f3e616eeafc310689accdd26efb90769596604a62c460ef8acece18bc +size 1734 diff --git a/selfdrive/assets/icons/arrow_right.png b/selfdrive/assets/icons/arrow_right.png deleted file mode 100644 index 9897695399..0000000000 --- a/selfdrive/assets/icons/arrow_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:edfbafa7fe485b20d26382f198856c3ea798e57826aa8c4553c49a35aee005ed -size 883 diff --git a/selfdrive/assets/icons/arrow_right.svg b/selfdrive/assets/icons/arrow_right.svg deleted file mode 100644 index c2df1310a2..0000000000 --- a/selfdrive/assets/icons/arrow_right.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fb66f3b15fc670cb9ea2b17fc75984cc3fc4f0118b880ce555d3cc7672b72fa5 -size 583 diff --git a/selfdrive/assets/icons/backspace.png b/selfdrive/assets/icons/backspace.png index ece10a3d73..16686be6f5 100644 --- a/selfdrive/assets/icons/backspace.png +++ b/selfdrive/assets/icons/backspace.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36e16def9e727216e491cafbb3fe655f82797389b37d63946f1a9c030ecc6f96 -size 4470 +oid sha256:576da562df8eb513e64ccb614ec727b257cbca8b5507974d01efc0f64c5382c2 +size 6267 diff --git a/selfdrive/assets/icons/backspace.svg b/selfdrive/assets/icons/backspace.svg deleted file mode 100644 index 8da38a106a..0000000000 --- a/selfdrive/assets/icons/backspace.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:10d92681ea39067298851a1d298959fbd1904e63431fbbcfff953f22a65b61c3 -size 468 diff --git a/selfdrive/assets/icons/capslock.png b/selfdrive/assets/icons/capslock.png new file mode 100644 index 0000000000..61414d7230 --- /dev/null +++ b/selfdrive/assets/icons/capslock.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6621d000d9514df1d636e9953941eadec415ff950724f26a61e0fcbaa7a3818 +size 5403 diff --git a/selfdrive/assets/icons/shift.png b/selfdrive/assets/icons/shift.png index 7026a807e7..de2a68b482 100644 --- a/selfdrive/assets/icons/shift.png +++ b/selfdrive/assets/icons/shift.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25abdab506c6ae81b0a379d86378e0ad73f6fa093be5dc81ed1f192e5b146044 -size 3113 +oid sha256:a93dd816bd0600ad47d10ebe530326bfa725dc31e4d5e1ee275f39b10f17a59d +size 4931 diff --git a/selfdrive/assets/icons/shift.svg b/selfdrive/assets/icons/shift.svg deleted file mode 100644 index 7f890856bd..0000000000 --- a/selfdrive/assets/icons/shift.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30f7cd752143d0d9148263d479d5c38809c9ecd64a0bfb5f0fe6d393faedcaa7 -size 232 diff --git a/selfdrive/assets/icons/shift_lock.png b/selfdrive/assets/icons/shift_lock.png deleted file mode 100644 index 1a0464ebcd..0000000000 --- a/selfdrive/assets/icons/shift_lock.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0796cb759559f1608a7498c2a00ca48e4d398e4a7b927778db0ae3f00b5c5bcb -size 3092 diff --git a/selfdrive/assets/icons/shift_lock.svg b/selfdrive/assets/icons/shift_lock.svg deleted file mode 100644 index 388e722b49..0000000000 --- a/selfdrive/assets/icons/shift_lock.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e9eeae279ca4022f109e8a1873def65edfc86108c5315e0af98e7f413db75218 -size 1074 diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh index 661464a5c6..d29ce3025d 100755 --- a/selfdrive/assets/prep-svg.sh +++ b/selfdrive/assets/prep-svg.sh @@ -2,6 +2,24 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ICONS_DIR="$DIR/icons" +BOOTSTRAP_SVG="$DIR/../../third_party/bootstrap/bootstrap-icons.svg" + +ICON_IDS=( + arrow-down + arrow-right + backspace + capslock + shift +) +ICON_FILL_COLOR="#fff" + +# extract bootstrap icons +for id in "${ICON_IDS[@]}"; do + svg="${ICONS_DIR}/${id}.svg" + perl -0777 -ne "print \$& if /]*id=\"$id\"[^>]*>.*?<\/symbol>/s" "$BOOTSTRAP_SVG" \ + | sed "s//<\/svg>/" > "$svg" +done # sudo apt install inkscape @@ -17,7 +35,12 @@ for svg in $(find $DIR -type f | grep svg$); do else export_dim="--export-height=512" fi - inkscape "$svg" --export-filename="$png" $export_dim + inkscape "$svg" --export-filename="$png" "$export_dim" optipng -o7 -strip all "$png" done + +# cleanup bootstrap SVGs +for id in "${ICON_IDS[@]}"; do + rm "${ICONS_DIR}/${id}.svg" +done From 5babe18184606b81b5829d028f28d7b18edc24e5 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 17 May 2025 02:24:04 +0800 Subject: [PATCH 074/142] system/ui/setup: fix WiFi manager UI element overlap issues (#35251) fix ui overlap issue --- system/ui/setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/ui/setup.py b/system/ui/setup.py index 7cf4cac9b4..2e40610395 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -144,6 +144,10 @@ class Setup: self.network_check_thread.join() def render_network_setup(self, rect: rl.Rectangle): + if self.wifi_ui.require_full_screen: + self.wifi_ui.render(rect) + return + title_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - MARGIN * 2, TITLE_FONT_SIZE) gui_label(title_rect, "Connect to Wi-Fi", TITLE_FONT_SIZE, font_weight=FontWeight.MEDIUM) From 71e3fd718342bfac57ee05c1e81518faf81aad94 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 17 May 2025 02:43:03 +0800 Subject: [PATCH 075/142] system/ui: use icon for special characters (#35248) * use icon for special characters * add icon for SHIFT_DOWN_KEY --- system/ui/lib/button.py | 44 +++++++++++++++++++++++++++-------- system/ui/widgets/keyboard.py | 14 ++++++++++- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index dfe75d88bd..2d58aee640 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -16,6 +16,7 @@ class TextAlignment(IntEnum): RIGHT = 2 +ICON_PADDING = 15 DEFAULT_BUTTON_FONT_SIZE = 60 BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255) BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51) @@ -46,6 +47,7 @@ def gui_button( border_radius: int = 10, # Corner rounding in pixels text_alignment: TextAlignment = TextAlignment.CENTER, text_padding: int = 20, # Padding for left/right alignment + icon = None, ) -> int: result = 0 @@ -68,20 +70,42 @@ def gui_button( rl.draw_rectangle_rounded(rect, roundness, 20, rl.BLACK) rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE) + # Handle icon and text positioning font = gui_app.font(font_weight) text_size = rl.measure_text_ex(font, text, font_size, 0) text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering - # Horizontal alignment - if text_alignment == TextAlignment.LEFT: - text_pos.x = rect.x + text_padding - elif text_alignment == TextAlignment.CENTER: - text_pos.x = rect.x + (rect.width - text_size.x) // 2 - elif text_alignment == TextAlignment.RIGHT: - text_pos.x = rect.x + rect.width - text_size.x - text_padding + # Draw icon if provided + if icon: + icon_y = rect.y + (rect.height - icon.height) / 2 + if text: + if text_alignment == TextAlignment.LEFT: + icon_x = rect.x + text_padding + text_pos.x = icon_x + icon.width + ICON_PADDING + elif text_alignment == TextAlignment.CENTER: + total_width = icon.width + ICON_PADDING + text_size.x + icon_x = rect.x + (rect.width - total_width) / 2 + text_pos.x = icon_x + icon.width + ICON_PADDING + else: # RIGHT + text_pos.x = rect.x + rect.width - text_size.x - text_padding + icon_x = text_pos.x - ICON_PADDING - icon.width + else: + # Center icon when no text + icon_x = rect.x + (rect.width - icon.width) / 2 - # Draw the button text - text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR - rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color) + rl.draw_texture_v(icon, rl.Vector2(icon_x, icon_y), rl.WHITE if is_enabled else rl.Color(255, 255, 255, 100)) + else: + # No icon, position text normally + if text_alignment == TextAlignment.LEFT: + text_pos.x = rect.x + text_padding + elif text_alignment == TextAlignment.CENTER: + text_pos.x = rect.x + (rect.width - text_size.x) // 2 + elif text_alignment == TextAlignment.RIGHT: + text_pos.x = rect.x + rect.width - text_size.x - text_padding + + # Draw the button text if any + if text: + text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR + rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color) return result diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 37580f80e5..aabd664171 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -55,6 +55,11 @@ class Keyboard: self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54) self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) + self._key_icons = { + BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 60, 60), + SHIFT_KEY: gui_app.texture("icons/shift.png", 60, 60), + SHIFT_DOWN_KEY: gui_app.texture("icons/arrow-down.png", 60, 60), + } @property def text(self): @@ -95,7 +100,14 @@ class Keyboard: start_x += new_width is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size - if gui_button(key_rect, key, is_enabled=is_enabled): + result = -1 + if key in self._key_icons: + texture = self._key_icons[key] + result = gui_button(key_rect, "", icon=texture, is_enabled=is_enabled) + else: + result = gui_button(key_rect, key, is_enabled=is_enabled) + + if result: if key == ENTER_KEY: return 1 else: From 09fde3c3ad2f15d27bbe47c0521ce7dd0f0bf0c0 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 17 May 2025 04:56:41 +0800 Subject: [PATCH 076/142] system/ui: Use OPENPILOT_URL instead of google.com for network connectivity check (#35254) check OPENPILOT_URL --- system/ui/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/setup.py b/system/ui/setup.py index 2e40610395..0ec5d6395e 100755 --- a/system/ui/setup.py +++ b/system/ui/setup.py @@ -123,7 +123,7 @@ class Setup: while not self.stop_network_check_thread.is_set(): if self.state == SetupState.NETWORK_SETUP: try: - urllib.request.urlopen("https://google.com", timeout=2) + urllib.request.urlopen(OPENPILOT_URL, timeout=2) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() From 8097a92515c0273a4ef0ef7b910093a7a8772da4 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Fri, 16 May 2025 22:24:03 +0100 Subject: [PATCH 077/142] zipapp pack (#35253) Used to ship python UI in agnos without an openpilot clone * add a main method to target * pack script * validate inputs * refactors * copy into temp, dont keep this * cleanup * help messages * rename to pack.py * pack.py * updates for device * moar * don't use cereal * just log normally * use importlib.resources * revert * Revert "don't use cereal" This reverts commit 7208524d422d88a1b07e209359aeb25e8b3bf4e7. * fix cereal? * cleanup * Revert "cleanup" This reverts commit 921edfe5020f244dbdf4f26767af7c98ca837d1c. * cython hotfix * Reapply "cleanup" This reverts commit 9b54552f784dea1b1eb4ffc03937571e4fc851ba. * more cleanup * any script? * slightly clearer * rm print * nothing python should use SVGs --------- Co-authored-by: Trey Moen --- cereal/__init__.py | 10 ++++--- release/pack.py | 50 +++++++++++++++++++++++++++++++++++ system/ui/lib/application.py | 16 ++++++----- system/ui/lib/wifi_manager.py | 13 ++++++--- system/ui/spinner.py | 6 ++++- 5 files changed, 79 insertions(+), 16 deletions(-) create mode 100755 release/pack.py diff --git a/cereal/__init__.py b/cereal/__init__.py index 89c5cf38e3..93f4d77227 100644 --- a/cereal/__init__.py +++ b/cereal/__init__.py @@ -1,9 +1,11 @@ import os import capnp +from importlib.resources import as_file, files -CEREAL_PATH = os.path.dirname(os.path.abspath(__file__)) capnp.remove_import_hook() -log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp")) -car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp")) -custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp")) +with as_file(files("cereal")) as fspath: + CEREAL_PATH = fspath.as_posix() + log = capnp.load(os.path.join(CEREAL_PATH, "log.capnp")) + car = capnp.load(os.path.join(CEREAL_PATH, "car.capnp")) + custom = capnp.load(os.path.join(CEREAL_PATH, "custom.capnp")) diff --git a/release/pack.py b/release/pack.py new file mode 100755 index 0000000000..1cb1a47a48 --- /dev/null +++ b/release/pack.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import importlib +import shutil +import sys +import tempfile +import zipapp +from argparse import ArgumentParser +from pathlib import Path + +from openpilot.common.basedir import BASEDIR + + +DIRS = ['cereal', 'openpilot'] +EXTS = ['.png', '.py', '.ttf', '.capnp'] +INTERPRETER = '/usr/bin/env python3' + + +def copy(src, dest): + if any(src.endswith(ext) for ext in EXTS): + shutil.copy2(src, dest, follow_symlinks=True) + + +if __name__ == '__main__': + parser = ArgumentParser(prog='pack.py', description="package script into a portable executable", epilog='comma.ai') + parser.add_argument('-e', '--entrypoint', help="function to call in module, default is 'main'", default='main') + parser.add_argument('-o', '--output', help='output file') + parser.add_argument('module', help="the module to target, e.g. 'openpilot.system.ui.spinner'") + args = parser.parse_args() + + if not args.output: + args.output = args.module + + try: + mod = importlib.import_module(args.module) + except ModuleNotFoundError: + print(f'{args.module} not found, typo?') + sys.exit(1) + + if not hasattr(mod, args.entrypoint): + print(f'{args.module} does not have a {args.entrypoint}() function, typo?') + sys.exit(1) + + with tempfile.TemporaryDirectory() as tmp: + for directory in DIRS: + shutil.copytree(BASEDIR + '/' + directory, tmp + '/' + directory, symlinks=False, dirs_exist_ok=True, copy_function=copy) + entry = f'{args.module}:{args.entrypoint}' + zipapp.create_archive(tmp, target=args.output, interpreter=INTERPRETER, main=entry) + + print(f'created executable {Path(args.output).resolve()}') diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 9e6c8ef28d..467cd8c5a8 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -3,7 +3,7 @@ import os import time import pyray as rl from enum import IntEnum -from openpilot.common.basedir import BASEDIR +from importlib.resources import as_file, files from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE @@ -18,9 +18,9 @@ STRICT_MODE = os.getenv("STRICT_MODE") == '1' DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE -ASSETS_DIR = os.path.join(BASEDIR, "selfdrive/assets") -FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts") +ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") +FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(IntEnum): BLACK = 0 @@ -74,7 +74,8 @@ class GuiApplication: if cache_key in self._textures: return self._textures[cache_key] - texture_obj = self._load_texture_from_image(os.path.join(ASSETS_DIR, asset_path), width, height, alpha_premultiply, keep_aspect_ratio) + with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: + texture_obj = self._load_texture_from_image(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) self._textures[cache_key] = texture_obj return texture_obj @@ -163,9 +164,10 @@ class GuiApplication: ) for index, font_file in enumerate(font_files): - font = rl.load_font_ex(os.path.join(FONT_DIR, font_file), 120, None, 0) - rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) - self._fonts[index] = font + with as_file(FONT_DIR.joinpath(font_file)) as fspath: + font = rl.load_font_ex(fspath.as_posix(), 120, None, 0) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + self._fonts[index] = font rl.gui_set_font(self._fonts[FontWeight.NORMAL]) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 7070266aab..4ac7466cb7 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -13,7 +13,11 @@ from dbus_next.aio import MessageBus from dbus_next import BusType, Variant, Message from dbus_next.errors import DBusError from dbus_next.constants import MessageType -from openpilot.common.params import Params +try: + from openpilot.common.params import Params +except ImportError: + # Params/Cythonized modules are not available in zipapp + Params = None from openpilot.common.swaglog import cloudlog T = TypeVar("T") @@ -81,9 +85,10 @@ class WifiManager: self.scan_task: asyncio.Task | None = None # Set tethering ssid as "weedle" + first 4 characters of a dongle id self._tethering_ssid = "weedle" - dongle_id = Params().get("DongleId", encoding="utf-8") - if dongle_id: - self._tethering_ssid += "-" + dongle_id[:4] + if Params is not None: + dongle_id = Params().get("DongleId", encoding="utf-8") + if dongle_id: + self._tethering_ssid += "-" + dongle_id[:4] self.running: bool = True self._current_connection_ssid: str | None = None diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 5fd3f964a6..2af24c4e51 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -98,7 +98,11 @@ class Spinner(BaseWindow[SpinnerRenderer]): self.update(str(round(100 * cur / total))) -if __name__ == "__main__": +def main(): with Spinner() as s: s.update("Spinner text") time.sleep(5) + + +if __name__ == "__main__": + main() From da670108e7f3f9bb391b3067330e957fd33300bc Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sat, 17 May 2025 00:23:55 +0100 Subject: [PATCH 078/142] system/ui: all font weights (#35255) --- system/ui/lib/application.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 467cd8c5a8..a2ea392784 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -23,14 +23,15 @@ ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(IntEnum): - BLACK = 0 - BOLD = 1 - EXTRA_BOLD = 2 - EXTRA_LIGHT = 3 + THIN = 0 + EXTRA_LIGHT = 1 + LIGHT = 2 + NORMAL = 3 MEDIUM = 4 - NORMAL = 5 - SEMI_BOLD = 6 - THIN = 7 + SEMI_BOLD = 5 + BOLD = 6 + EXTRA_BOLD = 7 + BLACK = 8 class GuiApplication: @@ -153,14 +154,15 @@ class GuiApplication: def _load_fonts(self): font_files = ( - "Inter-Black.ttf", + "Inter-Thin.ttf", + "Inter-ExtraLight.ttf", + "Inter-Light.ttf", + "Inter-Regular.ttf", + "Inter-Medium.ttf", + "Inter-SemiBold.ttf", "Inter-Bold.ttf", "Inter-ExtraBold.ttf", - "Inter-ExtraLight.ttf", - "Inter-Medium.ttf", - "Inter-Regular.ttf", - "Inter-SemiBold.ttf", - "Inter-Thin.ttf" + "Inter-Black.ttf", ) for index, font_file in enumerate(font_files): From 49c422e8723f12a14f68a10fcd7154a7c03fe3c8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 16 May 2025 21:29:16 -0700 Subject: [PATCH 079/142] Tesla: fix angle control saturation (#35256) * we shouldn't need this * or this? * stash junk * hmm * comment * cc * sadly we have to maintain behavior * bump --- selfdrive/controls/lib/latcontrol_angle.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 1b249a3d1a..f94dca57c1 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -10,6 +10,7 @@ class LatControlAngle(LatControl): def __init__(self, CP, CI): super().__init__(CP, CI) self.sat_check_min_speed = 5. + self.use_steer_limited_by_controls = CP.brand == "tesla" def update(self, active, CS, VM, params, steer_limited_by_controls, desired_curvature, calibrated_pose, curvature_limited): angle_log = log.ControlsState.LateralAngleState.new_message() @@ -22,7 +23,13 @@ class LatControlAngle(LatControl): angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) angle_steers_des += params.angleOffsetDeg - angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD + if self.use_steer_limited_by_controls: + # these cars' carcontrolers calculate max lateral accel and jerk, so we can rely on carOutput for saturation + angle_control_saturated = steer_limited_by_controls + else: + # for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota) + # or relying on EPS (Ford Q3), carOutput does not capture maxing out torque # TODO: this can be improved + angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD angle_log.saturated = bool(self._check_saturation(angle_control_saturated, CS, False, curvature_limited)) angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) angle_log.steeringAngleDesiredDeg = angle_steers_des From 5a67aa3618cd81eb06f8d1fe30f1df84914c75a5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 17 May 2025 12:00:09 -0700 Subject: [PATCH 080/142] test_translations: don't fail on rate limit (#35262) skip translation test on 429 --- selfdrive/ui/tests/test_translations.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 09dd7c5d8b..2ae3356bb8 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -96,8 +96,15 @@ class TestTranslations: match = re.search(r'_([a-zA-Z]{2,3})', self.file) assert match, f"{self.name} - could not parse language" - response = requests.get(f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}") - response.raise_for_status() + try: + response = requests.get( + f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}" + ) + response.raise_for_status() + except requests.exceptions.HTTPError as e: + if e.response is not None and e.response.status_code == 429: + pytest.skip("word list rate limited") + raise banned_words = {line.strip() for line in response.text.splitlines()} From 1c68a073a023ce1a80204183fb2886d6e077db21 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 17 May 2025 12:00:24 -0700 Subject: [PATCH 081/142] feat(clip): enable ecam (#35259) --- tools/clip/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index f0c4369c6f..a84290f9c4 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -234,7 +234,7 @@ def clip( out, ] - replay_cmd = [REPLAY, '-c', '1', '-s', str(begin_at), '--prefix', prefix] + replay_cmd = [REPLAY, '--ecam', '-c', '1', '-s', str(begin_at), '--prefix', prefix] if data_dir: replay_cmd.extend(['--data_dir', data_dir]) if quality == 'low': From 77102a85322d799a12af98ea114c895ddaed7fe8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 17 May 2025 17:31:27 -0700 Subject: [PATCH 082/142] Ford: support low gear (#35238) support low gear for ford --- selfdrive/car/car_specific.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index dc37519be1..355ab1a7c8 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -46,7 +46,7 @@ class CarSpecificEvents: events = Events() elif self.CP.brand == 'ford': - events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.manumatic]) + events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.low, GearShifter.manumatic]) elif self.CP.brand == 'nissan': events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.brake]) From 065e0e6369a8c19b8b364fa6e5e871019dc93c93 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sat, 17 May 2025 19:44:52 -0700 Subject: [PATCH 083/142] [bot] Update Python packages (#35264) Update Python packages Co-authored-by: Vehicle Researcher --- docs/CARS.md | 629 +++++++++++++++++++++++++------------------------- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 109 ++++----- 5 files changed, 373 insertions(+), 371 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 55871eba2f..cdaeb38890 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,321 +4,322 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 311 Supported Cars +# 312 Supported Cars -|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| -|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica Hybrid 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Mustang Mach-E 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Performance Trim) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 Electrified (Australia Only) 2022[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 Electrified (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV80 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback Hybrid 2023 (Europe only)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 6 (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Cruz 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2023-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Plug-in Hybrid 2024[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (Southeast Asia only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2023-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan[7](#footnotes)|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan[7](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan[7](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan[7](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Å koda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Å koda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Å koda|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Å koda|Kodiaq 2017-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Å koda|Octavia 2015-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Å koda|Octavia RS 2016[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Å koda|Octavia Scout 2017-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Å koda|Scala 2020-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Å koda|Superb 2015-22[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Tesla[11](#footnotes)|Model Y (with HW4) 2024[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-24|All|openpilot|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)|| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| +|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +|Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Chrysler|Pacifica Hybrid 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=MewJc9LYp9M| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=MewJc9LYp9M| +|Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ford|Mustang Mach-E 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://www.youtube.com/watch?v=uUGkH6C_EQU| +|Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G80 (2.5T Advanced Trim, with HDA II) 2024[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|G90 2017-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV60 (Performance Trim) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV70 (2.5T Trim, without HDA II) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV70 (3.5T Trim, without HDA II) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV70 Electrified (Australia Only) 2022[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV70 Electrified (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Genesis|GV80 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic Hatchback Hybrid 2023 (Europe only)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Civic Hatchback Hybrid 2025|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|CR-V 2015-16|Touring Trim|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|HR-V 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Honda|Ridgeline 2017-25|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq 5 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq 5 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq 6 (with HDA II) 2023-24[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Santa Cruz 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson 2023-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Tucson Plug-in Hybrid 2024[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|EV6 (Southeast Asia only) 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|EV6 (with HDA II) 2022-24[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|EV6 (without HDA II) 2022-24[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV (with HDA II) 2025[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro EV (without HDA II) 2023-25[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Hybrid 2018|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sportage 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RC 2023|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Nissan[7](#footnotes)|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Nissan[7](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Nissan[7](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Nissan[7](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ram connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://youtu.be/uaISd1j7Z4U| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Rivian A connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||https://youtu.be/uaISd1j7Z4U| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Subaru|Ascent 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Forester 2019-21|All[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2020-21|EyeSight Driver Assistance[8](#footnotes)|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Å koda|Fabia 2022-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Å koda|Kamiq 2021-23[13,15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Å koda|Karoq 2019-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Å koda|Kodiaq 2017-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Å koda|Octavia 2015-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Å koda|Octavia RS 2016[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Å koda|Octavia Scout 2017-19[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Å koda|Scala 2020-23[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Å koda|Superb 2015-22[15](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model 3 (with HW3) 2019-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model 3 (with HW4) 2024-25[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model Y (with HW3) 2020-23[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Tesla[11](#footnotes)|Model Y (with HW4) 2024[10](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Camry 2018-20|All|Stock|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Camry 2021-24|All|openpilot|0 mph[12](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v3
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Passat 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[17](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
||| ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `nightly-dev`.
diff --git a/opendbc_repo b/opendbc_repo index f960080e28..51203122f3 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f960080e286401cdb432709589d582a6c065fd10 +Subproject commit 51203122f30162dcf778b700deb482b8b5ab3b72 diff --git a/panda b/panda index 6adac802b5..8bd83750c2 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 6adac802b5dce739e35b734b81b8d20fca4bf700 +Subproject commit 8bd83750c2c0a5b8f68a00717f7516ae612fb5ab diff --git a/tinygrad_repo b/tinygrad_repo index f726f79a9e..6f77b938d7 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit f726f79a9e1b3ab6dafb3dc012feb7901afdf721 +Subproject commit 6f77b938d77395a34f02c7d7d904d80fcd81b9cd diff --git a/uv.lock b/uv.lock index 5eafe7b636..cd9c84af69 100644 --- a/uv.lock +++ b/uv.lock @@ -1544,11 +1544,11 @@ wheels = [ [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] @@ -1612,16 +1612,16 @@ wheels = [ [[package]] name = "protobuf" -version = "6.30.2" +version = "6.31.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/8c/cf2ac658216eebe49eaedf1e06bc06cbf6a143469236294a1171a51357c3/protobuf-6.30.2.tar.gz", hash = "sha256:35c859ae076d8c56054c25b59e5e59638d86545ed6e2b6efac6be0b6ea3ba048", size = 429315, upload-time = "2025-03-26T19:12:57.394Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/48/718c1e104a2e89970a8ff3b06d87e152834b576c570a6908f8c17ba88d65/protobuf-6.31.0.tar.gz", hash = "sha256:314fab1a6a316469dc2dd46f993cbbe95c861ea6807da910becfe7475bc26ffe", size = 441644, upload-time = "2025-05-14T17:58:27.862Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/85/cd53abe6a6cbf2e0029243d6ae5fb4335da2996f6c177bb2ce685068e43d/protobuf-6.30.2-cp310-abi3-win32.whl", hash = "sha256:b12ef7df7b9329886e66404bef5e9ce6a26b54069d7f7436a0853ccdeb91c103", size = 419148, upload-time = "2025-03-26T19:12:41.359Z" }, - { url = "https://files.pythonhosted.org/packages/97/e9/7b9f1b259d509aef2b833c29a1f3c39185e2bf21c9c1be1cd11c22cb2149/protobuf-6.30.2-cp310-abi3-win_amd64.whl", hash = "sha256:7653c99774f73fe6b9301b87da52af0e69783a2e371e8b599b3e9cb4da4b12b9", size = 431003, upload-time = "2025-03-26T19:12:44.156Z" }, - { url = "https://files.pythonhosted.org/packages/8e/66/7f3b121f59097c93267e7f497f10e52ced7161b38295137a12a266b6c149/protobuf-6.30.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:0eb523c550a66a09a0c20f86dd554afbf4d32b02af34ae53d93268c1f73bc65b", size = 417579, upload-time = "2025-03-26T19:12:45.447Z" }, - { url = "https://files.pythonhosted.org/packages/d0/89/bbb1bff09600e662ad5b384420ad92de61cab2ed0f12ace1fd081fd4c295/protobuf-6.30.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:50f32cc9fd9cb09c783ebc275611b4f19dfdfb68d1ee55d2f0c7fa040df96815", size = 317319, upload-time = "2025-03-26T19:12:46.999Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/1925de813499546bc8ab3ae857e3ec84efe7d2f19b34529d0c7c3d02d11d/protobuf-6.30.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4f6c687ae8efae6cf6093389a596548214467778146b7245e886f35e1485315d", size = 316212, upload-time = "2025-03-26T19:12:48.458Z" }, - { url = "https://files.pythonhosted.org/packages/e5/a1/93c2acf4ade3c5b557d02d500b06798f4ed2c176fa03e3c34973ca92df7f/protobuf-6.30.2-py3-none-any.whl", hash = "sha256:ae86b030e69a98e08c77beab574cbcb9fff6d031d57209f574a5aea1445f4b51", size = 167062, upload-time = "2025-03-26T19:12:55.892Z" }, + { url = "https://files.pythonhosted.org/packages/b6/77/8671682038b08237c927215fa3296bc1c54e4086fe542c87017c1b626663/protobuf-6.31.0-cp310-abi3-win32.whl", hash = "sha256:10bd62802dfa0588649740a59354090eaf54b8322f772fbdcca19bc78d27f0d6", size = 423437, upload-time = "2025-05-14T17:58:16.116Z" }, + { url = "https://files.pythonhosted.org/packages/e4/07/cc9b0cbf7593f6ef8cf87fa9b0e55cd74c5cb526dd89ad84aa7d6547ef8d/protobuf-6.31.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e987c99fd634be8347246a02123250f394ba20573c953de133dc8b2c107dd71", size = 435118, upload-time = "2025-05-14T17:58:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/21/46/33f884aa8bc59114dc97e0d954ca4618c556483670236008c88fbb7e834f/protobuf-6.31.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c812f0f96ceb6b514448cefeb1df54ec06dde456783f5099c0e2f8a0f2caa89", size = 425439, upload-time = "2025-05-14T17:58:19.709Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/9a676b50229ce37b12777d7b21de90ae7bc0f9505d07e72e2e8d47b8d165/protobuf-6.31.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:67ce50195e4e584275623b8e6bc6d3d3dfd93924bf6116b86b3b8975ab9e4571", size = 321950, upload-time = "2025-05-14T17:58:22.04Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a7/243fa2d3c1b7675d54744b32dacf30356f4c27c0d3ad940ca8745a1c6b2c/protobuf-6.31.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:5353e38844168a327acd2b2aa440044411cd8d1b6774d5701008bd1dba067c79", size = 320904, upload-time = "2025-05-14T17:58:23.438Z" }, + { url = "https://files.pythonhosted.org/packages/ee/01/1ed1d482960a5718fd99c82f6d79120181947cfd4667ec3944d448ed44a3/protobuf-6.31.0-py3-none-any.whl", hash = "sha256:6ac2e82556e822c17a8d23aa1190bbc1d06efb9c261981da95c71c9da09e9e23", size = 168558, upload-time = "2025-05-14T17:58:26.923Z" }, ] [[package]] @@ -1740,20 +1740,21 @@ wheels = [ [[package]] name = "pycryptodome" -version = "3.22.0" +version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/e6/099310419df5ada522ff34ffc2f1a48a11b37fc6a76f51a6854c182dbd3e/pycryptodome-3.22.0.tar.gz", hash = "sha256:fd7ab568b3ad7b77c908d7c3f7e167ec5a8f035c64ff74f10d47a4edd043d723", size = 4917300, upload-time = "2025-03-15T23:03:36.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/65/a05831c3e4bcd1bf6c2a034e399f74b3d6f30bb4e37e36b9c310c09dc8c0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:009e1c80eea42401a5bd5983c4bab8d516aef22e014a4705622e24e6d9d703c6", size = 2490637, upload-time = "2025-03-15T23:02:43.111Z" }, - { url = "https://files.pythonhosted.org/packages/5c/76/ff3c2e7a60d17c080c4c6120ebaf60f38717cd387e77f84da4dcf7f64ff0/pycryptodome-3.22.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3b76fa80daeff9519d7e9f6d9e40708f2fce36b9295a847f00624a08293f4f00", size = 1635372, upload-time = "2025-03-15T23:02:45.564Z" }, - { url = "https://files.pythonhosted.org/packages/cc/7f/cc5d6da0dbc36acd978d80a72b228e33aadaec9c4f91c93221166d8bdc05/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a31fa5914b255ab62aac9265654292ce0404f6b66540a065f538466474baedbc", size = 2177456, upload-time = "2025-03-15T23:02:47.688Z" }, - { url = "https://files.pythonhosted.org/packages/92/65/35f5063e68790602d892ad36e35ac723147232a9084d1999630045c34593/pycryptodome-3.22.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0092fd476701eeeb04df5cc509d8b739fa381583cda6a46ff0a60639b7cd70d", size = 2263744, upload-time = "2025-03-15T23:02:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/cc/67/46acdd35b1081c3dbc72dc466b1b95b80d2f64cad3520f994a9b6c5c7d00/pycryptodome-3.22.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d5b0ddc7cf69231736d778bd3ae2b3efb681ae33b64b0c92fb4626bb48bb89", size = 2303356, upload-time = "2025-03-15T23:02:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f9/a4f8a83384626098e3f55664519bec113002b9ef751887086ae63a53135a/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f6cf6aa36fcf463e622d2165a5ad9963b2762bebae2f632d719dfb8544903cf5", size = 2176714, upload-time = "2025-03-15T23:02:53.85Z" }, - { url = "https://files.pythonhosted.org/packages/88/65/e5f8c3a885f70a6e05c84844cd5542120576f4369158946e8cfc623a464d/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:aec7b40a7ea5af7c40f8837adf20a137d5e11a6eb202cde7e588a48fb2d871a8", size = 2337329, upload-time = "2025-03-15T23:02:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/b8/2a/25e0be2b509c28375c7f75c7e8d8d060773f2cce4856a1654276e3202339/pycryptodome-3.22.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d21c1eda2f42211f18a25db4eaf8056c94a8563cd39da3683f89fe0d881fb772", size = 2262255, upload-time = "2025-03-15T23:02:58.055Z" }, - { url = "https://files.pythonhosted.org/packages/41/58/60917bc4bbd91712e53ce04daf237a74a0ad731383a01288130672994328/pycryptodome-3.22.0-cp37-abi3-win32.whl", hash = "sha256:f02baa9f5e35934c6e8dcec91fcde96612bdefef6e442813b8ea34e82c84bbfb", size = 1763403, upload-time = "2025-03-15T23:03:00.616Z" }, - { url = "https://files.pythonhosted.org/packages/55/f4/244c621afcf7867e23f63cfd7a9630f14cfe946c9be7e566af6c3915bcde/pycryptodome-3.22.0-cp37-abi3-win_amd64.whl", hash = "sha256:d086aed307e96d40c23c42418cbbca22ecc0ab4a8a0e24f87932eeab26c08627", size = 1794568, upload-time = "2025-03-15T23:03:03.189Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, ] [[package]] @@ -4744,27 +4745,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.11.9" +version = "0.11.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134, upload-time = "2025-05-09T16:19:41.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453, upload-time = "2025-05-09T16:18:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566, upload-time = "2025-05-09T16:19:01.432Z" }, - { url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020, upload-time = "2025-05-09T16:19:03.897Z" }, - { url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935, upload-time = "2025-05-09T16:19:06.455Z" }, - { url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971, upload-time = "2025-05-09T16:19:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631, upload-time = "2025-05-09T16:19:12.307Z" }, - { url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236, upload-time = "2025-05-09T16:19:15.006Z" }, - { url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436, upload-time = "2025-05-09T16:19:17.063Z" }, - { url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759, upload-time = "2025-05-09T16:19:19.693Z" }, - { url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985, upload-time = "2025-05-09T16:19:21.831Z" }, - { url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775, upload-time = "2025-05-09T16:19:24.401Z" }, - { url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957, upload-time = "2025-05-09T16:19:27.08Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307, upload-time = "2025-05-09T16:19:29.462Z" }, - { url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026, upload-time = "2025-05-09T16:19:31.569Z" }, - { url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627, upload-time = "2025-05-09T16:19:33.657Z" }, - { url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340, upload-time = "2025-05-09T16:19:35.815Z" }, - { url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080, upload-time = "2025-05-09T16:19:39.605Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" }, + { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" }, + { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" }, + { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" }, ] [[package]] @@ -4823,11 +4824,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.4.0" +version = "80.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/0cc40fe41fd2adb80a2f388987f4f8db3c866c69e33e0b4c8b093fdf700e/setuptools-80.4.0.tar.gz", hash = "sha256:5a78f61820bc088c8e4add52932ae6b8cf423da2aff268c23f813cfbb13b4006", size = 1315008, upload-time = "2025-05-09T20:42:27.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188, upload-time = "2025-05-15T02:41:00.955Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/93/dba5ed08c2e31ec7cdc2ce75705a484ef0be1a2fecac8a58272489349de8/setuptools-80.4.0-py3-none-any.whl", hash = "sha256:6cdc8cb9a7d590b237dbe4493614a9b75d0559b888047c1f67d49ba50fc3edb2", size = 1200812, upload-time = "2025-05-09T20:42:25.325Z" }, + { url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776, upload-time = "2025-05-15T02:40:58.887Z" }, ] [[package]] @@ -4906,17 +4907,17 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.5.1" +version = "0.5.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/2d/b04ae180312b81dbb694504bee170eada5372242e186f6298139fd3a0513/sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041", size = 52896, upload-time = "2024-10-12T09:40:12.24Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/a6/91e9f08ed37c7c9f56b5227c6aea7f2ae63ba2d59520eefb24e82cbdd589/sounddevice-0.5.2.tar.gz", hash = "sha256:c634d51bd4e922d6f0fa5e1a975cc897c947f61d31da9f79ba7ea34dff448b49", size = 53150, upload-time = "2025-05-16T18:12:27.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/d1/464b5fca3decdd0cfec8c47f7b4161a0b12972453201c1bf03811f367c5e/sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c", size = 32276, upload-time = "2024-10-12T09:40:05.605Z" }, - { url = "https://files.pythonhosted.org/packages/6f/f6/6703fe7cf3d7b7279040c792aeec6334e7305956aba4a80f23e62c8fdc44/sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031", size = 107916, upload-time = "2024-10-12T09:40:07.436Z" }, - { url = "https://files.pythonhosted.org/packages/57/a5/78a5e71f5ec0faedc54f4053775d61407bfbd7d0c18228c7f3d4252fd276/sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055", size = 312494, upload-time = "2024-10-12T09:40:09.355Z" }, - { url = "https://files.pythonhosted.org/packages/af/9b/15217b04f3b36d30de55fef542389d722de63f1ad81f9c72d8afc98cb6ab/sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1", size = 363634, upload-time = "2024-10-12T09:40:11.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/2d/582738fc01352a5bc20acac9221e58538365cecb3bb264838f66419df219/sounddevice-0.5.2-py3-none-any.whl", hash = "sha256:82375859fac2e73295a4ab3fc60bd4782743157adc339561c1f1142af472f505", size = 32450, upload-time = "2025-05-16T18:12:21.919Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6f/e3dd751face4fcb5be25e8abba22f25d8e6457ebd7e9ed79068b768dc0e5/sounddevice-0.5.2-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:943f27e66037d41435bdd0293454072cdf657b594c9cde63cd01ee3daaac7ab3", size = 108088, upload-time = "2025-05-16T18:12:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/bfad79af0b380aa7c0bfe73e4b03e0af45354a48ad62549489bd7696c5b0/sounddevice-0.5.2-py3-none-win32.whl", hash = "sha256:3a113ce614a2c557f14737cb20123ae6298c91fc9301eb014ada0cba6d248c5f", size = 312665, upload-time = "2025-05-16T18:12:24.726Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3e/61d88e6b0a7383127cdc779195cb9d83ebcf11d39bc961de5777e457075e/sounddevice-0.5.2-py3-none-win_amd64.whl", hash = "sha256:e18944b767d2dac3771a7771bdd7ff7d3acd7d334e72c4bedab17d1aed5dbc22", size = 363808, upload-time = "2025-05-16T18:12:26Z" }, ] [[package]] @@ -4989,14 +4990,14 @@ wheels = [ [[package]] name = "types-requests" -version = "2.32.0.20250328" +version = "2.32.0.20250515" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/7d/eb174f74e3f5634eaacb38031bbe467dfe2e545bc255e5c90096ec46bc46/types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32", size = 22995, upload-time = "2025-03-28T02:55:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/c1/cdc4f9b8cfd9130fbe6276db574f114541f4231fcc6fb29648289e6e3390/types_requests-2.32.0.20250515.tar.gz", hash = "sha256:09c8b63c11318cb2460813871aaa48b671002e59fda67ca909e9883777787581", size = 23012, upload-time = "2025-05-15T03:04:31.817Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/15/3700282a9d4ea3b37044264d3e4d1b1f0095a4ebf860a99914fd544e3be3/types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2", size = 20663, upload-time = "2025-03-28T02:55:11.946Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/68a997c73a129287785f418c1ebb6004f81e46b53b3caba88c0e03fcd04a/types_requests-2.32.0.20250515-py3-none-any.whl", hash = "sha256:f8eba93b3a892beee32643ff836993f15a785816acca21ea0ffa006f05ef0fb2", size = 20635, upload-time = "2025-05-15T03:04:30.5Z" }, ] [[package]] From 8427bf76cfc3c217188fa833ab0065aa5dc18f0e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 17 May 2025 22:54:01 -0700 Subject: [PATCH 084/142] typo --- selfdrive/controls/lib/latcontrol_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index f94dca57c1..f3f1bc5acf 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -24,7 +24,7 @@ class LatControlAngle(LatControl): angle_steers_des += params.angleOffsetDeg if self.use_steer_limited_by_controls: - # these cars' carcontrolers calculate max lateral accel and jerk, so we can rely on carOutput for saturation + # these cars' carcontrollers calculate max lateral accel and jerk, so we can rely on carOutput for saturation angle_control_saturated = steer_limited_by_controls else: # for cars which use a method of limiting torque such as a torque signal (Nissan and Toyota) From 66c0971a0f95f0af21ad2948198cad53c6ab1700 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 17 May 2025 23:56:21 -0700 Subject: [PATCH 085/142] migration: remove disengage on gas alternative experience (#35267) * fix * done * test * update refs --- selfdrive/test/process_replay/migration.py | 14 ++++++++------ selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 02cd9b6d71..33b363cfd9 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -22,12 +22,12 @@ MigrationOps = tuple[list[tuple[int, capnp.lib.capnp._DynamicStructReader]], lis MigrationFunc = Callable[[list[MessageWithIndex]], MigrationOps] -## rules for migration functions -## 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature -## 2. it only gets the messages that are in the inputs list -## 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr -## 4. it must return a list of operations to be applied to the logreader (replace, add, delete) -## 5. all migration functions must be independent of each other +# rules for migration functions +# 1. must use the decorator @migration(inputs=[...], product="...") and MigrationFunc signature +# 2. it only gets the messages that are in the inputs list +# 3. product is the message type created by the migration function, and the function will be skipped if product type already exists in lr +# 4. it must return a list of operations to be applied to the logreader (replace, add, delete) +# 5. all migration functions must be independent of each other def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: bool = False, camera_states: bool = False): migrations = [ migrate_sensorEvents, @@ -306,6 +306,8 @@ def migrate_pandaStates(msgs): elif msg.which() == 'pandaStates': new_msg = msg.as_builder() new_msg.pandaStates[-1].safetyParam = safety_param + # Clear DISABLE_DISENGAGE_ON_GAS bit to fix controls mismatch + new_msg.pandaStates[-1].alternativeExperience &= ~1 ops.append((index, new_msg.as_reader())) return ops, [], [] diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c97f9a6bde..c8e879e58a 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -bc0a2e6eac422e574b6c670fe493d368df10ef42 \ No newline at end of file +6602b004aa91decf3a1a4ab7ddaf77a99d7ebfc3 \ No newline at end of file From 33721cf5fab014f945df783fc3267f4081c23401 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 18 May 2025 00:38:57 -0700 Subject: [PATCH 086/142] Lower Tesla steer limit timer (#35268) * bump * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 51203122f3..488e31ccba 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 51203122f30162dcf778b700deb482b8b5ab3b72 +Subproject commit 488e31ccbab507315648061c23308cedc5284cf2 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c8e879e58a..8e8a88db2c 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -6602b004aa91decf3a1a4ab7ddaf77a99d7ebfc3 \ No newline at end of file +2d23b1ba470927cefa646b95e1c8b7ac0f944f60 \ No newline at end of file From a6a09276e94da4b526de4f7604b1a6b448842426 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 18 May 2025 00:52:02 -0700 Subject: [PATCH 087/142] Hyundai: fix recent enable button press regression (#35265) * move back to port * much better * use the CS field now * bump * fix * need to switch so it works on ports that don't use it, and makes more sense * bump * bump * update refs * make sense * master --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 15 +++------------ selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 488e31ccba..0af0d464df 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 488e31ccbab507315648061c23308cedc5284cf2 +Subproject commit 0af0d464df30daa4c78f5349c958ba584370ef80 diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 355ab1a7c8..67d1010d75 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -1,11 +1,8 @@ -from collections import deque from cereal import car, log import cereal.messaging as messaging from opendbc.car import DT_CTRL, structs from opendbc.car.interfaces import MAX_CTRL_SPEED from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams -from opendbc.car.hyundai.interface import ENABLE_BUTTONS as HYUNDAI_ENABLE_BUTTONS -from opendbc.car.hyundai.carstate import PREV_BUTTON_SAMPLES as HYUNDAI_PREV_BUTTON_SAMPLES from openpilot.selfdrive.selfdrived.events import Events @@ -39,8 +36,6 @@ class CarSpecificEvents: self.no_steer_warning = False self.silent_steer_warning = True - self.cruise_buttons: deque = deque([], maxlen=HYUNDAI_PREV_BUTTON_SAMPLES) - def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): if self.CP.brand in ('body', 'mock'): events = Events() @@ -136,12 +131,8 @@ class CarSpecificEvents: # events.add(EventName.steerTimeLimit) elif self.CP.brand == 'hyundai': - # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state - # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons - # Main button also can trigger an engagement on these cars - self.cruise_buttons.append(any(ev.type in HYUNDAI_ENABLE_BUTTONS for ev in CS.buttonEvents)) events = self.create_common_events(CS, CS_prev, extra_gears=(GearShifter.sport, GearShifter.manumatic), - pcm_enable=self.CP.pcmCruise, allow_enable=any(self.cruise_buttons), allow_button_cancel=False) + pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: @@ -157,7 +148,7 @@ class CarSpecificEvents: return events def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears=None, pcm_enable=True, - allow_enable=True, allow_button_cancel=True): + allow_button_cancel=True): events = Events() if CS.doorOpen: @@ -236,7 +227,7 @@ class CarSpecificEvents: # we engage when pcm is active (rising edge) # enabling can optionally be blocked by the car interface if pcm_enable: - if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and allow_enable: + if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled and not CS.blockPcmEnable: events.add(EventName.pcmEnable) elif not CS.cruiseState.enabled: events.add(EventName.pcmDisable) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 8e8a88db2c..f67c4540c6 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -2d23b1ba470927cefa646b95e1c8b7ac0f944f60 \ No newline at end of file +746da1900c43a64146bb485b7fc2a836e2005113 \ No newline at end of file From 8e57f774d0e8302e1bf1834f84720bb1f9211ae3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 18 May 2025 01:05:15 -0700 Subject: [PATCH 088/142] Hyundai: move low speed steering alert to car state (#35269) * hkg can: move low speed alert to carstate * bump --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 0af0d464df..bf27fea810 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0af0d464df30daa4c78f5349c958ba584370ef80 +Subproject commit bf27fea810a111bd9b2c22a20e713485c65a2043 diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 67d1010d75..c5edb484af 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -134,14 +134,6 @@ class CarSpecificEvents: events = self.create_common_events(CS, CS_prev, extra_gears=(GearShifter.sport, GearShifter.manumatic), pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) - # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) - if CS.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: - self.low_speed_alert = True - if CS.vEgo > (self.CP.minSteerSpeed + 4.): - self.low_speed_alert = False - if self.low_speed_alert: - events.add(EventName.belowSteerSpeed) - else: events = self.create_common_events(CS, CS_prev) From e8fd8e601864b94e921878bd7359c8bd18562572 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 18 May 2025 01:20:28 -0700 Subject: [PATCH 089/142] bump opendbc --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index bf27fea810..447381dee2 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit bf27fea810a111bd9b2c22a20e713485c65a2043 +Subproject commit 447381dee2d30fb46e4bf1aec0529667d0b9d159 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f67c4540c6..f916e7b306 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -746da1900c43a64146bb485b7fc2a836e2005113 \ No newline at end of file +8e57f774d0e8302e1bf1834f84720bb1f9211ae3 \ No newline at end of file From f630cac06f40f4e52166c426befe77723cfbaaf7 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sun, 18 May 2025 15:57:45 +0300 Subject: [PATCH 090/142] modeld: replace CLANG=1 with CPU=1 (#35270) Replace CLANG=1 with CPU=1 --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/andiradulescu/openpilot?shareId=XXXX-XXXX-XXXX-XXXX). --- selfdrive/modeld/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 28d60b9e00..a6be5c15da 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -46,7 +46,7 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: elif arch == 'larch64': device_string = 'QCOM=1' elif arch == 'Darwin': - device_string = 'CLANG=1 IMAGE=0 JIT=2' + device_string = 'CPU=1 IMAGE=0 JIT=2' else: device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2' From ed0f8129955ddf727e937a01d240577814bcc6f2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 18 May 2025 16:21:20 -0700 Subject: [PATCH 091/142] system/ui: add SCALE env var (#35272) * ui: add SCALE env var * linting * cleaner --------- Co-authored-by: Cameron Clough --- system/ui/README.md | 1 + system/ui/lib/application.py | 37 +++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/system/ui/README.md b/system/ui/README.md index 5f47961a51..c71b77ab66 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -5,5 +5,6 @@ The user interfaces here are built with [raylib](https://www.raylib.com/). Quick start: * set `DEBUG_FPS=1` to show the FPS * set `STRICT_MODE=1` to kill the app if it drops too much below 60fps +* set `SCALE=1.5` to scale the entire UI by 1.5x * https://www.raylib.com/cheatsheet/cheatsheet.html * https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index a2ea392784..83ec0f6ddd 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -15,6 +15,7 @@ FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1" DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' +SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -22,6 +23,7 @@ DEFAULT_TEXT_COLOR = rl.WHITE ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") + class FontWeight(IntEnum): THIN = 0 EXTRA_LIGHT = 1 @@ -39,6 +41,10 @@ class GuiApplication: self._fonts: dict[FontWeight, rl.Font] = {} self._width = width self._height = height + self._scale = SCALE + self._scaled_width = int(self._width * self._scale) + self._scaled_height = int(self._height * self._scale) + self._render_texture: rl.RenderTexture | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -62,14 +68,15 @@ class GuiApplication: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT rl.set_config_flags(flags) - rl.init_window(self._width, self._height, title) + rl.init_window(self._scaled_width, self._scaled_height, title) + if self._scale != 1.0: + self._render_texture = rl.load_render_texture(self._width, self._height) rl.set_target_fps(fps) self._target_fps = fps self._set_styles() self._load_fonts() - def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" if cache_key in self._textures: @@ -80,7 +87,7 @@ class GuiApplication: self._textures[cache_key] = texture_obj return texture_obj - def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply = False, keep_aspect_ratio=True): + def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" image = rl.load_image(image_path) @@ -123,16 +130,32 @@ class GuiApplication: rl.unload_font(font) self._fonts = {} + if self._render_texture is not None: + rl.unload_render_texture(self._render_texture) + self._render_texture = None + rl.close_window() def render(self): try: while not (self._window_close_requested or rl.window_should_close()): - rl.begin_drawing() - rl.clear_background(rl.BLACK) + if self._render_texture: + rl.begin_texture_mode(self._render_texture) + rl.clear_background(rl.BLACK) + else: + rl.begin_drawing() + rl.clear_background(rl.BLACK) yield + if self._render_texture: + rl.end_texture_mode() + rl.begin_drawing() + rl.clear_background(rl.BLACK) + src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) + dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) + rl.draw_texture_pro(self._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) + if DEBUG_FPS: rl.draw_fps(10, 10) @@ -141,7 +164,7 @@ class GuiApplication: except KeyboardInterrupt: pass - def font(self, font_weight: FontWeight=FontWeight.NORMAL): + def font(self, font_weight: FontWeight = FontWeight.NORMAL): return self._fonts[font_weight] @property @@ -163,7 +186,7 @@ class GuiApplication: "Inter-Bold.ttf", "Inter-ExtraBold.ttf", "Inter-Black.ttf", - ) + ) for index, font_file in enumerate(font_files): with as_file(FONT_DIR.joinpath(font_file)) as fspath: From e2569a8b322fbe105475e4d1336890fd79e2d522 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 00:27:35 +0100 Subject: [PATCH 092/142] Revert "system/ui: add SCALE env var (#35272)" This reverts commit ed0f8129955ddf727e937a01d240577814bcc6f2. --- system/ui/README.md | 1 - system/ui/lib/application.py | 37 +++++++----------------------------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/system/ui/README.md b/system/ui/README.md index c71b77ab66..5f47961a51 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -5,6 +5,5 @@ The user interfaces here are built with [raylib](https://www.raylib.com/). Quick start: * set `DEBUG_FPS=1` to show the FPS * set `STRICT_MODE=1` to kill the app if it drops too much below 60fps -* set `SCALE=1.5` to scale the entire UI by 1.5x * https://www.raylib.com/cheatsheet/cheatsheet.html * https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 83ec0f6ddd..a2ea392784 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -15,7 +15,6 @@ FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1" DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' -SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -23,7 +22,6 @@ DEFAULT_TEXT_COLOR = rl.WHITE ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") - class FontWeight(IntEnum): THIN = 0 EXTRA_LIGHT = 1 @@ -41,10 +39,6 @@ class GuiApplication: self._fonts: dict[FontWeight, rl.Font] = {} self._width = width self._height = height - self._scale = SCALE - self._scaled_width = int(self._width * self._scale) - self._scaled_height = int(self._height * self._scale) - self._render_texture: rl.RenderTexture | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -68,15 +62,14 @@ class GuiApplication: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT rl.set_config_flags(flags) - rl.init_window(self._scaled_width, self._scaled_height, title) - if self._scale != 1.0: - self._render_texture = rl.load_render_texture(self._width, self._height) + rl.init_window(self._width, self._height, title) rl.set_target_fps(fps) self._target_fps = fps self._set_styles() self._load_fonts() + def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" if cache_key in self._textures: @@ -87,7 +80,7 @@ class GuiApplication: self._textures[cache_key] = texture_obj return texture_obj - def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): + def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply = False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" image = rl.load_image(image_path) @@ -130,32 +123,16 @@ class GuiApplication: rl.unload_font(font) self._fonts = {} - if self._render_texture is not None: - rl.unload_render_texture(self._render_texture) - self._render_texture = None - rl.close_window() def render(self): try: while not (self._window_close_requested or rl.window_should_close()): - if self._render_texture: - rl.begin_texture_mode(self._render_texture) - rl.clear_background(rl.BLACK) - else: - rl.begin_drawing() - rl.clear_background(rl.BLACK) + rl.begin_drawing() + rl.clear_background(rl.BLACK) yield - if self._render_texture: - rl.end_texture_mode() - rl.begin_drawing() - rl.clear_background(rl.BLACK) - src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) - dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) - rl.draw_texture_pro(self._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) - if DEBUG_FPS: rl.draw_fps(10, 10) @@ -164,7 +141,7 @@ class GuiApplication: except KeyboardInterrupt: pass - def font(self, font_weight: FontWeight = FontWeight.NORMAL): + def font(self, font_weight: FontWeight=FontWeight.NORMAL): return self._fonts[font_weight] @property @@ -186,7 +163,7 @@ class GuiApplication: "Inter-Bold.ttf", "Inter-ExtraBold.ttf", "Inter-Black.ttf", - ) + ) for index, font_file in enumerate(font_files): with as_file(FONT_DIR.joinpath(font_file)) as fspath: From 15c7d556f61d9dd61a420a8d1f6e165b3d3db184 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 00:33:52 +0100 Subject: [PATCH 093/142] Reapply "system/ui: add SCALE env var (#35272)" (#35273) * Reapply "system/ui: add SCALE env var (#35272)" This reverts commit e2569a8b322fbe105475e4d1336890fd79e2d522. * set mouse scale factor --- system/ui/README.md | 1 + system/ui/lib/application.py | 38 +++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/system/ui/README.md b/system/ui/README.md index 5f47961a51..c71b77ab66 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -5,5 +5,6 @@ The user interfaces here are built with [raylib](https://www.raylib.com/). Quick start: * set `DEBUG_FPS=1` to show the FPS * set `STRICT_MODE=1` to kill the app if it drops too much below 60fps +* set `SCALE=1.5` to scale the entire UI by 1.5x * https://www.raylib.com/cheatsheet/cheatsheet.html * https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index a2ea392784..19f01ca6ac 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -15,6 +15,7 @@ FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions ENABLE_VSYNC = os.getenv("ENABLE_VSYNC") == "1" DEBUG_FPS = os.getenv("DEBUG_FPS") == '1' STRICT_MODE = os.getenv("STRICT_MODE") == '1' +SCALE = float(os.getenv("SCALE", "1.0")) DEFAULT_TEXT_SIZE = 60 DEFAULT_TEXT_COLOR = rl.WHITE @@ -22,6 +23,7 @@ DEFAULT_TEXT_COLOR = rl.WHITE ASSETS_DIR = files("openpilot.selfdrive").joinpath("assets") FONT_DIR = ASSETS_DIR.joinpath("fonts") + class FontWeight(IntEnum): THIN = 0 EXTRA_LIGHT = 1 @@ -39,6 +41,10 @@ class GuiApplication: self._fonts: dict[FontWeight, rl.Font] = {} self._width = width self._height = height + self._scale = SCALE + self._scaled_width = int(self._width * self._scale) + self._scaled_height = int(self._height * self._scale) + self._render_texture: rl.RenderTexture | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -62,14 +68,16 @@ class GuiApplication: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT rl.set_config_flags(flags) - rl.init_window(self._width, self._height, title) + rl.init_window(self._scaled_width, self._scaled_height, title) + if self._scale != 1.0: + rl.set_mouse_scale(1 / self._scale, 1 / self._scale) + self._render_texture = rl.load_render_texture(self._width, self._height) rl.set_target_fps(fps) self._target_fps = fps self._set_styles() self._load_fonts() - def texture(self, asset_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" if cache_key in self._textures: @@ -80,7 +88,7 @@ class GuiApplication: self._textures[cache_key] = texture_obj return texture_obj - def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply = False, keep_aspect_ratio=True): + def _load_texture_from_image(self, image_path: str, width: int, height: int, alpha_premultiply=False, keep_aspect_ratio=True): """Load and resize a texture, storing it for later automatic unloading.""" image = rl.load_image(image_path) @@ -123,16 +131,32 @@ class GuiApplication: rl.unload_font(font) self._fonts = {} + if self._render_texture is not None: + rl.unload_render_texture(self._render_texture) + self._render_texture = None + rl.close_window() def render(self): try: while not (self._window_close_requested or rl.window_should_close()): - rl.begin_drawing() - rl.clear_background(rl.BLACK) + if self._render_texture: + rl.begin_texture_mode(self._render_texture) + rl.clear_background(rl.BLACK) + else: + rl.begin_drawing() + rl.clear_background(rl.BLACK) yield + if self._render_texture: + rl.end_texture_mode() + rl.begin_drawing() + rl.clear_background(rl.BLACK) + src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) + dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) + rl.draw_texture_pro(self._render_texture.texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) + if DEBUG_FPS: rl.draw_fps(10, 10) @@ -141,7 +165,7 @@ class GuiApplication: except KeyboardInterrupt: pass - def font(self, font_weight: FontWeight=FontWeight.NORMAL): + def font(self, font_weight: FontWeight = FontWeight.NORMAL): return self._fonts[font_weight] @property @@ -163,7 +187,7 @@ class GuiApplication: "Inter-Bold.ttf", "Inter-ExtraBold.ttf", "Inter-Black.ttf", - ) + ) for index, font_file in enumerate(font_files): with as_file(FONT_DIR.joinpath(font_file)) as fspath: From 645bd391b793f343822c4eb5e2ded225019674b6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 18 May 2025 16:57:56 -0700 Subject: [PATCH 094/142] readme: add sgo to branch guide --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 77002481d3..b0248b0f54 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

openpilot is an operating system for robotics.
- Currently, it upgrades the driver assistance system in 275+ supported cars. + Currently, it upgrades the driver assistance system in 300+ supported cars.

@@ -57,6 +57,7 @@ We have detailed instructions for [how to install the harness and device in a ca | `release3-staging` | openpilot-test.comma.ai | This is the staging branch for releases. Use it to get new releases slightly early. | | `nightly` | openpilot-nightly.comma.ai | This is the bleeding edge development branch. Do not expect this to be stable. | | `nightly-dev` | installer.comma.ai/commaai/nightly-dev | Same as nightly, but includes experimental development features for some cars. | +| `secretgoodopenpilot` | installer.comma.ai/commaai/secretgoodopenpilot | This is a preview branch from the autonomy team where new driving models get merged earlier than master. | To start developing openpilot ------ @@ -83,8 +84,8 @@ Safety and Testing * panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile). * We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes. -Licensing ------- +
+MIT Licensed openpilot is released under the MIT license. Some parts of the software are released under other licenses as specified. @@ -93,9 +94,10 @@ Any user of this software shall indemnify and hold harmless Comma.ai, Inc. and i **THIS IS ALPHA QUALITY SOFTWARE FOR RESEARCH PURPOSES ONLY. THIS IS NOT A PRODUCT. YOU ARE RESPONSIBLE FOR COMPLYING WITH LOCAL LAWS AND REGULATIONS. NO WARRANTY EXPRESSED OR IMPLIED.** +
-User Data and comma Account ------- +
+User Data and comma Account By default, openpilot uploads the driving data to our servers. You can also access your data through [comma connect](https://connect.comma.ai/). We use your data to train better models and improve openpilot for everyone. @@ -105,3 +107,4 @@ openpilot logs the road-facing cameras, CAN, GPS, IMU, magnetometer, thermal sen The driver-facing camera is only logged if you explicitly opt-in in settings. The microphone is not recorded. By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data. +
From d7bf309eca1ae078d064d3587b0c9ae74a6ff2a1 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 00:58:33 +0100 Subject: [PATCH 095/142] system/ui: update keyboard enter key (#35275) * system/ui: update keyboard enter key - use arrow-right icon - use primary color * format --- system/ui/widgets/keyboard.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index aabd664171..ec6a70fb0a 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,13 +1,13 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.button import gui_button +from openpilot.system.ui.lib.button import ButtonStyle, gui_button from openpilot.system.ui.lib.inputbox import InputBox from openpilot.system.ui.lib.label import gui_label # Constants for special keys CONTENT_MARGIN = 50 BACKSPACE_KEY = "<-" -ENTER_KEY = "Enter" +ENTER_KEY = "->" SPACE_KEY = " " SHIFT_KEY = "↑" SHIFT_DOWN_KEY = "↓" @@ -59,6 +59,7 @@ class Keyboard: BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 60, 60), SHIFT_KEY: gui_app.texture("icons/shift.png", 60, 60), SHIFT_DOWN_KEY: gui_app.texture("icons/arrow-down.png", 60, 60), + ENTER_KEY: gui_app.texture("icons/arrow-right.png", 60, 60), } @property @@ -103,7 +104,7 @@ class Keyboard: result = -1 if key in self._key_icons: texture = self._key_icons[key] - result = gui_button(key_rect, "", icon=texture, is_enabled=is_enabled) + result = gui_button(key_rect, "", icon=texture, button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.NORMAL, is_enabled=is_enabled) else: result = gui_button(key_rect, key, is_enabled=is_enabled) From f1ba6c389b9f545cb40a0c72e5851e7737993a1c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 18 May 2025 16:59:53 -0700 Subject: [PATCH 096/142] quick repo garbage collection (#35274) * start gc * lil more * debug * no sound * add back --- SConstruct | 12 +------ common/ffi_wrapper.py | 8 ----- common/filter_simple.py | 1 - pyproject.toml | 2 +- selfdrive/debug/{ => car}/clear_dtc.py | 0 .../{ => car}/hyundai_enable_radar_points.py | 0 .../debug/{ => car}/toyota_eps_factor.py | 0 selfdrive/debug/{ => car}/vw_mqb_config.py | 0 selfdrive/debug/measure_modeld_packet_drop.py | 33 ------------------- selfdrive/debug/show_matching_cars.py | 20 ----------- selfdrive/ui/qt/python_helpers.py | 5 ++- system/athena/athenad.py | 2 +- system/camerad/{snapshot => }/snapshot.py | 0 system/camerad/snapshot/__init__.py | 0 system/camerad/test/check_skips.py | 27 --------------- .../test/get_thumbnails_for_segment.py | 24 -------------- system/camerad/test/test_exposure.py | 2 +- 17 files changed, 8 insertions(+), 128 deletions(-) delete mode 100644 common/ffi_wrapper.py rename selfdrive/debug/{ => car}/clear_dtc.py (100%) rename selfdrive/debug/{ => car}/hyundai_enable_radar_points.py (100%) rename selfdrive/debug/{ => car}/toyota_eps_factor.py (100%) rename selfdrive/debug/{ => car}/vw_mqb_config.py (100%) delete mode 100755 selfdrive/debug/measure_modeld_packet_drop.py delete mode 100755 selfdrive/debug/show_matching_cars.py rename system/camerad/{snapshot => }/snapshot.py (100%) delete mode 100644 system/camerad/snapshot/__init__.py delete mode 100755 system/camerad/test/check_skips.py delete mode 100755 system/camerad/test/get_thumbnails_for_segment.py diff --git a/SConstruct b/SConstruct index c40f118985..d5597a70fe 100644 --- a/SConstruct +++ b/SConstruct @@ -55,11 +55,6 @@ AddOption('--external-sconscript', dest='external_sconscript', help='add an external SConscript to the build') -AddOption('--pc-thneed', - action='store_true', - dest='pc_thneed', - help='use thneed on pc') - AddOption('--mutation', action='store_true', help='generate mutation-ready code') @@ -288,12 +283,7 @@ else: elif arch != "Darwin": qt_libs += ["GL"] qt_env['QT3DIR'] = qt_env['QTDIR'] - -# compatibility for older SCons versions -try: - qt_env.Tool('qt3') -except SCons.Errors.UserError: - qt_env.Tool('qt') +qt_env.Tool('qt3') qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] qt_flags = [ diff --git a/common/ffi_wrapper.py b/common/ffi_wrapper.py deleted file mode 100644 index 01741c6f42..0000000000 --- a/common/ffi_wrapper.py +++ /dev/null @@ -1,8 +0,0 @@ -import platform - - -def suffix(): - if platform.system() == "Darwin": - return ".dylib" - else: - return ".so" diff --git a/common/filter_simple.py b/common/filter_simple.py index 0ec7a51562..9ea6fe3070 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -1,5 +1,4 @@ class FirstOrderFilter: - # first order filter def __init__(self, x0, rc, dt, initialized=True): self.x = x0 self.dt = dt diff --git a/pyproject.toml b/pyproject.toml index 20bfebd938..464b605f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,7 @@ tools = [ ] [project.urls] -Homepage = "https://comma.ai" +Homepage = "https://github.com/commaai/openpilot" [build-system] requires = ["hatchling"] diff --git a/selfdrive/debug/clear_dtc.py b/selfdrive/debug/car/clear_dtc.py similarity index 100% rename from selfdrive/debug/clear_dtc.py rename to selfdrive/debug/car/clear_dtc.py diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/car/hyundai_enable_radar_points.py similarity index 100% rename from selfdrive/debug/hyundai_enable_radar_points.py rename to selfdrive/debug/car/hyundai_enable_radar_points.py diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/car/toyota_eps_factor.py similarity index 100% rename from selfdrive/debug/toyota_eps_factor.py rename to selfdrive/debug/car/toyota_eps_factor.py diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/car/vw_mqb_config.py similarity index 100% rename from selfdrive/debug/vw_mqb_config.py rename to selfdrive/debug/car/vw_mqb_config.py diff --git a/selfdrive/debug/measure_modeld_packet_drop.py b/selfdrive/debug/measure_modeld_packet_drop.py deleted file mode 100755 index 9814942ce2..0000000000 --- a/selfdrive/debug/measure_modeld_packet_drop.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python3 -import cereal.messaging as messaging - -if __name__ == "__main__": - modeld_sock = messaging.sub_sock("modelV2") - - last_frame_id = None - start_t: int | None = None - frame_cnt = 0 - dropped = 0 - - while True: - m = messaging.recv_one(modeld_sock) - if m is None: - continue - - frame_id = m.modelV2.frameId - t = m.logMonoTime / 1e9 - frame_cnt += 1 - - if start_t is None: - start_t = t - last_frame_id = frame_id - continue - - d_frame = frame_id - last_frame_id - dropped += d_frame - 1 - - expected_num_frames = int((t - start_t) * 20) - frame_drop = 100 * (1 - (expected_num_frames / frame_cnt)) - print(f"Num dropped {dropped}, Drop compared to 20Hz: {frame_drop:.2f}%") - - last_frame_id = frame_id diff --git a/selfdrive/debug/show_matching_cars.py b/selfdrive/debug/show_matching_cars.py deleted file mode 100755 index bc13772977..0000000000 --- a/selfdrive/debug/show_matching_cars.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python3 -from opendbc.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars -import cereal.messaging as messaging - - -# rav4 2019 and corolla tss2 -fingerprint = {896: 8, 898: 8, 900: 6, 976: 1, 1541: 8, 902: 6, 905: 8, 810: 2, 1164: 8, 1165: 8, 1166: 8, 1167: 8, 1552: 8, 1553: 8, 1556: 8, 1571: 8, 921: 8, 1056: 8, 544: 4, 1570: 8, 1059: 1, 36: 8, 37: 8, 550: 8, 935: 8, 552: 4, 170: 8, 812: 8, 944: 8, 945: 8, 562: 6, 180: 8, 1077: 8, 951: 8, 1592: 8, 1076: 8, 186: 4, 955: 8, 956: 8, 1001: 8, 705: 8, 452: 8, 1788: 8, 464: 8, 824: 8, 466: 8, 467: 8, 761: 8, 728: 8, 1572: 8, 1114: 8, 933: 8, 800: 8, 608: 8, 865: 8, 610: 8, 1595: 8, 934: 8, 998: 5, 1745: 8, 1000: 8, 764: 8, 1002: 8, 999: 7, 1789: 8, 1649: 8, 1779: 8, 1568: 8, 1017: 8, 1786: 8, 1787: 8, 1020: 8, 426: 6, 1279: 8} # noqa: E501 - -candidate_cars = all_legacy_fingerprint_cars() - - -for addr, l in fingerprint.items(): - dat = messaging.new_message('can', 1) - - msg = dat.can[0] - msg.address = addr - msg.dat = " " * l - - candidate_cars = eliminate_incompatible_cars(msg, candidate_cars) - print(candidate_cars) diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py index 88c36290d9..1f6d43f309 100644 --- a/selfdrive/ui/qt/python_helpers.py +++ b/selfdrive/ui/qt/python_helpers.py @@ -1,11 +1,14 @@ import os +import platform from cffi import FFI import sip -from openpilot.common.ffi_wrapper import suffix from openpilot.common.basedir import BASEDIR +def suffix(): + return ".dylib" if platform.system() == "Darwin" else ".so" + def get_ffi(): lib = os.path.join(BASEDIR, "selfdrive", "ui", "qt", "libpython_helpers" + suffix()) diff --git a/system/athena/athenad.py b/system/athena/athenad.py index fb546fa40f..5a02d45c43 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -546,7 +546,7 @@ def getNetworks(): @dispatcher.add_method def takeSnapshot() -> str | dict[str, str] | None: - from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot + from openpilot.system.camerad.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: def b64jpeg(x): diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot.py similarity index 100% rename from system/camerad/snapshot/snapshot.py rename to system/camerad/snapshot.py diff --git a/system/camerad/snapshot/__init__.py b/system/camerad/snapshot/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/system/camerad/test/check_skips.py b/system/camerad/test/check_skips.py deleted file mode 100755 index 0814ce44ff..0000000000 --- a/system/camerad/test/check_skips.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# type: ignore -import cereal.messaging as messaging - -all_sockets = ['roadCameraState', 'driverCameraState', 'wideRoadCameraState'] -prev_id = [None,None,None] -this_id = [None,None,None] -dt = [None,None,None] -num_skipped = [0,0,0] - -if __name__ == "__main__": - sm = messaging.SubMaster(all_sockets) - while True: - sm.update() - - for i in range(len(all_sockets)): - if not sm.updated[all_sockets[i]]: - continue - this_id[i] = sm[all_sockets[i]].frameId - if prev_id[i] is None: - prev_id[i] = this_id[i] - continue - dt[i] = this_id[i] - prev_id[i] - if dt[i] != 1: - num_skipped[i] += dt[i] - 1 - print(all_sockets[i] ,dt[i] - 1, num_skipped[i]) - prev_id[i] = this_id[i] diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py deleted file mode 100755 index 21409f398d..0000000000 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -from tqdm import tqdm - -from openpilot.tools.lib.logreader import LogReader - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("route", help="The route name") - args = parser.parse_args() - - out_path = os.path.join("jpegs", f"{args.route.replace('|', '_').replace('/', '_')}") - os.makedirs(out_path, exist_ok=True) - - lr = LogReader(args.route) - - for msg in tqdm(lr): - if msg.which() == 'thumbnail': - with open(os.path.join(out_path, f"{msg.thumbnail.frameId}.jpg"), 'wb') as f: - f.write(msg.thumbnail.thumbnail) - elif msg.which() == 'navThumbnail': - with open(os.path.join(out_path, f"nav_{msg.navThumbnail.frameId}.jpg"), 'wb') as f: - f.write(msg.navThumbnail.thumbnail) diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py index 97f03ed182..f431c03410 100644 --- a/system/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -3,7 +3,7 @@ import numpy as np import pytest from openpilot.selfdrive.test.helpers import with_processes -from openpilot.system.camerad.snapshot.snapshot import get_snapshots +from openpilot.system.camerad.snapshot import get_snapshots TEST_TIME = 45 REPEAT = 5 From 66409866223afca81b3ec98636f6c61e9c5919e8 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 01:31:38 +0100 Subject: [PATCH 097/142] system/ui: increase keyboard key font/icon size (#35276) --- system/ui/widgets/keyboard.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index ec6a70fb0a..bceffad943 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -4,6 +4,8 @@ from openpilot.system.ui.lib.button import ButtonStyle, gui_button from openpilot.system.ui.lib.inputbox import InputBox from openpilot.system.ui.lib.label import gui_label +KEY_FONT_SIZE = 96 + # Constants for special keys CONTENT_MARGIN = 50 BACKSPACE_KEY = "<-" @@ -56,10 +58,10 @@ class Keyboard: self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54) self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) self._key_icons = { - BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 60, 60), - SHIFT_KEY: gui_app.texture("icons/shift.png", 60, 60), - SHIFT_DOWN_KEY: gui_app.texture("icons/arrow-down.png", 60, 60), - ENTER_KEY: gui_app.texture("icons/arrow-right.png", 60, 60), + BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 80, 80), + SHIFT_KEY: gui_app.texture("icons/shift.png", 80, 80), + SHIFT_DOWN_KEY: gui_app.texture("icons/arrow-down.png", 80, 80), + ENTER_KEY: gui_app.texture("icons/arrow-right.png", 80, 80), } @property @@ -106,7 +108,7 @@ class Keyboard: texture = self._key_icons[key] result = gui_button(key_rect, "", icon=texture, button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.NORMAL, is_enabled=is_enabled) else: - result = gui_button(key_rect, key, is_enabled=is_enabled) + result = gui_button(key_rect, key, KEY_FONT_SIZE, is_enabled=is_enabled) if result: if key == ENTER_KEY: From 0c013f61863230e22e8491cfd8945c600229e691 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 11:28:04 +0100 Subject: [PATCH 098/142] system/ui: add caps lock to keyboard (#35277) * add new icons shift-fill and capslock-fill, rm capslock * SHIFT_KEY_ON, SHIFT_KEY_OFF * capslock * rm arrow-down * a lot simpler * only one * just use time * layout name * rename shift * CONSTANT --- selfdrive/assets/icons/arrow-down.png | 3 -- selfdrive/assets/icons/capslock-fill.png | 3 ++ selfdrive/assets/icons/capslock.png | 3 -- selfdrive/assets/icons/shift-fill.png | 3 ++ selfdrive/assets/prep-svg.sh | 4 +- system/ui/widgets/keyboard.py | 53 +++++++++++++++++------- 6 files changed, 45 insertions(+), 24 deletions(-) delete mode 100644 selfdrive/assets/icons/arrow-down.png create mode 100644 selfdrive/assets/icons/capslock-fill.png delete mode 100644 selfdrive/assets/icons/capslock.png create mode 100644 selfdrive/assets/icons/shift-fill.png diff --git a/selfdrive/assets/icons/arrow-down.png b/selfdrive/assets/icons/arrow-down.png deleted file mode 100644 index 834c1cb8b9..0000000000 --- a/selfdrive/assets/icons/arrow-down.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e12a3cf36fdef107d237457e20b44e16320414f9de8a1791aff1ec5fd85ccef -size 2390 diff --git a/selfdrive/assets/icons/capslock-fill.png b/selfdrive/assets/icons/capslock-fill.png new file mode 100644 index 0000000000..66854e78f2 --- /dev/null +++ b/selfdrive/assets/icons/capslock-fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6872a1047f1a534a037be7b1367640fe1bfb205a6e1c50420a2d1a946cda78ed +size 4397 diff --git a/selfdrive/assets/icons/capslock.png b/selfdrive/assets/icons/capslock.png deleted file mode 100644 index 61414d7230..0000000000 --- a/selfdrive/assets/icons/capslock.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c6621d000d9514df1d636e9953941eadec415ff950724f26a61e0fcbaa7a3818 -size 5403 diff --git a/selfdrive/assets/icons/shift-fill.png b/selfdrive/assets/icons/shift-fill.png new file mode 100644 index 0000000000..1ce02d5822 --- /dev/null +++ b/selfdrive/assets/icons/shift-fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e625ca991746abaaac375b095aa9a586601982232f4aae0fc2b17b2a524e9ce9 +size 3946 diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh index d29ce3025d..2332cd25c5 100755 --- a/selfdrive/assets/prep-svg.sh +++ b/selfdrive/assets/prep-svg.sh @@ -6,11 +6,11 @@ ICONS_DIR="$DIR/icons" BOOTSTRAP_SVG="$DIR/../../third_party/bootstrap/bootstrap-icons.svg" ICON_IDS=( - arrow-down arrow-right backspace - capslock + capslock-fill shift + shift-fill ) ICON_FILL_COLOR="#fff" diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index bceffad943..5eebb49c83 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,3 +1,5 @@ +import time +from typing import Literal import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.button import ButtonStyle, gui_button @@ -5,30 +7,32 @@ from openpilot.system.ui.lib.inputbox import InputBox from openpilot.system.ui.lib.label import gui_label KEY_FONT_SIZE = 96 +DOUBLE_CLICK_THRESHOLD = 0.5 # seconds # Constants for special keys CONTENT_MARGIN = 50 BACKSPACE_KEY = "<-" ENTER_KEY = "->" SPACE_KEY = " " -SHIFT_KEY = "↑" -SHIFT_DOWN_KEY = "↓" +SHIFT_INACTIVE_KEY = "SHIFT_OFF" +SHIFT_ACTIVE_KEY = "SHIFT_ON" +CAPS_LOCK_KEY = "CAPS" NUMERIC_KEY = "123" SYMBOL_KEY = "#+=" ABC_KEY = "ABC" # Define keyboard layouts as a dictionary for easier access -keyboard_layouts = { +KEYBOARD_LAYOUTS = { "lowercase": [ ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], ["a", "s", "d", "f", "g", "h", "j", "k", "l"], - [SHIFT_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY], + [SHIFT_INACTIVE_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY], [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], ], "uppercase": [ ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], ["A", "S", "D", "F", "G", "H", "J", "K", "L"], - [SHIFT_DOWN_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY], + [SHIFT_ACTIVE_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY], [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], ], "numbers": [ @@ -48,7 +52,10 @@ keyboard_layouts = { class Keyboard: def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False): - self._layout = keyboard_layouts["lowercase"] + self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase" + self._caps_lock = False + self._last_shift_press_time = 0 + self._max_text_size = max_text_size self._min_text_size = min_text_size self._input_box = InputBox(max_text_size) @@ -59,8 +66,9 @@ class Keyboard: self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) self._key_icons = { BACKSPACE_KEY: gui_app.texture("icons/backspace.png", 80, 80), - SHIFT_KEY: gui_app.texture("icons/shift.png", 80, 80), - SHIFT_DOWN_KEY: gui_app.texture("icons/arrow-down.png", 80, 80), + SHIFT_INACTIVE_KEY: gui_app.texture("icons/shift.png", 80, 80), + SHIFT_ACTIVE_KEY: gui_app.texture("icons/shift-fill.png", 80, 80), + CAPS_LOCK_KEY: gui_app.texture("icons/capslock-fill.png", 80, 80), ENTER_KEY: gui_app.texture("icons/arrow-right.png", 80, 80), } @@ -84,13 +92,15 @@ class Keyboard: input_box_rect = rl.Rectangle(rect.x + input_margin, rect.y + 160, rect.width - input_margin, 100) self._render_input_area(input_box_rect) + layout = KEYBOARD_LAYOUTS[self._layout_name] + h_space, v_space = 15, 15 row_y_start = rect.y + 300 # Starting Y position for the first row key_height = (rect.height - 300 - 3 * v_space) / 4 - key_max_width = (rect.width - (len(self._layout[2]) - 1) * h_space) / len(self._layout[2]) + key_max_width = (rect.width - (len(layout[2]) - 1) * h_space) / len(layout[2]) # Iterate over the rows of keys in the current layout - for row, keys in enumerate(self._layout): + for row, keys in enumerate(layout): key_width = min((rect.width - (180 if row == 1 else 0) - h_space * (len(keys) - 1)) / len(keys), key_max_width) start_x = rect.x + (90 if row == 1 else 0) @@ -105,6 +115,8 @@ class Keyboard: is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size result = -1 if key in self._key_icons: + if key == SHIFT_ACTIVE_KEY and self._caps_lock: + key = CAPS_LOCK_KEY texture = self._key_icons[key] result = gui_button(key_rect, "", icon=texture, button_style=ButtonStyle.PRIMARY if key == ENTER_KEY else ButtonStyle.NORMAL, is_enabled=is_enabled) else: @@ -148,18 +160,27 @@ class Keyboard: ) def handle_key_press(self, key): - if key in (SHIFT_DOWN_KEY, ABC_KEY): - self._layout = keyboard_layouts["lowercase"] - elif key == SHIFT_KEY: - self._layout = keyboard_layouts["uppercase"] + if key in (CAPS_LOCK_KEY, ABC_KEY): + self._caps_lock = False + self._layout_name = "lowercase" + elif key == SHIFT_INACTIVE_KEY: + self._last_shift_press_time = time.monotonic() + self._layout_name = "uppercase" + elif key == SHIFT_ACTIVE_KEY: + if time.monotonic() - self._last_shift_press_time < DOUBLE_CLICK_THRESHOLD: + self._caps_lock = True + else: + self._layout_name = "lowercase" elif key == NUMERIC_KEY: - self._layout = keyboard_layouts["numbers"] + self._layout_name = "numbers" elif key == SYMBOL_KEY: - self._layout = keyboard_layouts["specials"] + self._layout_name = "specials" elif key == BACKSPACE_KEY: self._input_box.delete_char_before_cursor() else: self._input_box.add_char_at_cursor(key) + if not self._caps_lock and self._layout_name == "uppercase": + self._layout_name = "lowercase" if __name__ == "__main__": From 9bbbff14d5f282833216124a39a0f629f79f569c Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 11:36:04 +0100 Subject: [PATCH 099/142] system/ui: don't reset on PC --- system/ui/reset.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/ui/reset.py b/system/ui/reset.py index 80a1c10ea8..20b689934b 100755 --- a/system/ui/reset.py +++ b/system/ui/reset.py @@ -5,6 +5,7 @@ import sys import threading from enum import IntEnum +from openpilot.system.hardware import PC from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.label import gui_label, gui_text_box @@ -31,7 +32,10 @@ class Reset: self.mode = mode self.reset_state = ResetState.NONE - def do_reset(self): + def _do_erase(self): + if PC: + return + # Best effort to wipe NVME os.system(f"sudo umount {NVME}") os.system(f"yes | sudo mkfs.ext4 {NVME}") @@ -48,7 +52,7 @@ class Reset: def start_reset(self): self.reset_state = ResetState.RESETTING - threading.Timer(0.1, self.do_reset).start() + threading.Timer(0.1, self._do_erase).start() def render(self, rect: rl.Rectangle): label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100) From 38c1bd096b5ec0f7bca5bed8700bb5899077b7a1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 19 May 2025 21:56:45 +0800 Subject: [PATCH 100/142] system/ui: Improve UI rendering smoothness for scaled display (#35280) improve smoothness --- system/ui/lib/application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 19f01ca6ac..8639a0bda5 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -72,6 +72,7 @@ class GuiApplication: if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) self._render_texture = rl.load_render_texture(self._width, self._height) + rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) rl.set_target_fps(fps) self._target_fps = fps From 6eecb4f986df2e35386eabfec4cf3b4e3a4d534a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 17:31:42 +0100 Subject: [PATCH 101/142] system/ui: network widget improvements (#35284) * larger font size for confirmation dialog * try this * forget btn color * text color * font size * caps * Revert "caps" This reverts commit a3e6cfbf053e46158036728cbcd9d8bd9f99d5e5. * too much * fixme * do that? * keyboard: reset state on clear --- system/ui/lib/button.py | 12 ++++++++++-- system/ui/widgets/confirm_dialog.py | 1 + system/ui/widgets/keyboard.py | 2 ++ system/ui/widgets/network.py | 8 ++++---- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 2d58aee640..69a5da646e 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -8,6 +8,7 @@ class ButtonStyle(IntEnum): PRIMARY = 1 # For main actions DANGER = 2 # For critical actions, like reboot or delete TRANSPARENT = 3 # For buttons with transparent background and border + ACTION = 4 class TextAlignment(IntEnum): @@ -20,6 +21,8 @@ ICON_PADDING = 15 DEFAULT_BUTTON_FONT_SIZE = 60 BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255) BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51) +ACTION_BUTTON_FONT_SIZE = 48 +ACTION_BUTTON_TEXT_COLOR = rl.Color(0, 0, 0, 255) BUTTON_BACKGROUND_COLORS = { @@ -27,6 +30,7 @@ BUTTON_BACKGROUND_COLORS = { ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255), ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, + ButtonStyle.ACTION: rl.Color(189, 189, 189, 255), } BUTTON_PRESSED_BACKGROUND_COLORS = { @@ -34,6 +38,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255), ButtonStyle.DANGER: rl.Color(255, 36, 36, 255), ButtonStyle.TRANSPARENT: rl.BLACK, + ButtonStyle.ACTION: rl.Color(130, 130, 130, 255), } @@ -47,13 +52,16 @@ def gui_button( border_radius: int = 10, # Corner rounding in pixels text_alignment: TextAlignment = TextAlignment.CENTER, text_padding: int = 20, # Padding for left/right alignment - icon = None, + icon=None, ) -> int: result = 0 if button_style in (ButtonStyle.PRIMARY, ButtonStyle.DANGER) and not is_enabled: button_style = ButtonStyle.NORMAL + if button_style == ButtonStyle.ACTION and font_size == DEFAULT_BUTTON_FONT_SIZE: + font_size = ACTION_BUTTON_FONT_SIZE + # Set background color based on button type bg_color = BUTTON_BACKGROUND_COLORS[button_style] if is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect): @@ -105,7 +113,7 @@ def gui_button( # Draw the button text if any if text: - text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR + text_color = ACTION_BUTTON_TEXT_COLOR if button_style == ButtonStyle.ACTION else BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR rl.draw_text_ex(font, text, text_pos, font_size, 0, text_color) return result diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 7a25682e27..f42220053b 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -34,6 +34,7 @@ def confirm_dialog(message: str, confirm_text: str, cancel_text: str = "Cancel") gui_text_box( text_rect, message, + font_size=88, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, ) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 5eebb49c83..85608c1ef5 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -77,6 +77,8 @@ class Keyboard: return self._input_box.text def clear(self): + self._layout_name = "lowercase" + self._caps_lock = False self._input_box.clear() def render(self, title: str, sub_title: str): diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 29d9a499da..32f8bcb4a4 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -3,7 +3,7 @@ from typing import Literal import pyray as rl from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.button import gui_button +from openpilot.system.ui.lib.button import ButtonStyle, gui_button from openpilot.system.ui.lib.label import gui_label from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import NetworkInfo, WifiManagerCallbacks, WifiManagerWrapper, SecurityType @@ -134,7 +134,7 @@ class WifiManagerUI: if status_text: status_text_rect = rl.Rectangle(security_icon_rect.x - 410, rect.y, 410, ITEM_HEIGHT) - rl.gui_label(status_text_rect, status_text) + gui_label(status_text_rect, status_text, font_size=48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) else: # If the network is saved, show the "Forget" button if network.is_saved: @@ -143,7 +143,7 @@ class WifiManagerUI: self.btn_width, 80, ) - if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget") and clicked: + if isinstance(self.state, StateIdle) and gui_button(forget_btn_rect, "Forget", button_style=ButtonStyle.ACTION) and clicked: self.state = StateShowForgetConfirm(network) self._draw_status_icon(security_icon_rect, network) @@ -152,7 +152,7 @@ class WifiManagerUI: if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), ssid_rect) and clicked: if not network.is_saved: self.state = StateNeedsAuth(network) - else: + elif not network.is_connected: self.connect_to_network(network) def _draw_status_icon(self, rect, network: NetworkInfo): From 41db0557e9ad3b865583bcb8e8e4ee686a6a4022 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 20 May 2025 01:56:25 +0800 Subject: [PATCH 102/142] system/ui: fix Wi-Fi conneciton flow for open networks (#35285) fix Wi-Fi conneciton flow for open networks --- system/ui/widgets/network.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 32f8bcb4a4..5877b3ff7a 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -150,21 +150,24 @@ class WifiManagerUI: self._draw_signal_strength_icon(signal_icon_rect, network) if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), ssid_rect) and clicked: - if not network.is_saved: + if not network.is_saved and network.security_type != SecurityType.OPEN: self.state = StateNeedsAuth(network) elif not network.is_connected: self.connect_to_network(network) def _draw_status_icon(self, rect, network: NetworkInfo): """Draw the status icon based on network's connection state""" - icon_file = "" + icon_file = None if network.is_connected: icon_file = "icons/checkmark.png" elif network.security_type == SecurityType.UNSUPPORTED: icon_file = "icons/circled_slash.png" - else: + elif network.security_type != SecurityType.OPEN: icon_file = "icons/lock_closed.png" + if not icon_file: + return + texture = gui_app.texture(icon_file, ICON_SIZE, ICON_SIZE) icon_rect = rl.Vector2(rect.x, rect.y + (ICON_SIZE - texture.height) / 2) rl.draw_texture_v(texture, icon_rect, rl.WHITE) From d6e9df98e3cb73de7e0c0448dc04844776435e5b Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 19 May 2025 19:18:48 +0100 Subject: [PATCH 103/142] system/ui: WPA3 is unsupported (#35286) --- system/ui/lib/wifi_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4ac7466cb7..96726bd999 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -596,7 +596,8 @@ class WifiManager: if flags == 0 and not (wpa_flags or rsn_flags): return SecurityType.OPEN if rsn_flags & 0x200: # SAE (WPA3 Personal) - return SecurityType.WPA3 + # TODO: support WPA3 + return SecurityType.UNSUPPORTED if rsn_flags: # RSN indicates WPA2 or higher return SecurityType.WPA2 if wpa_flags: # WPA flags indicate WPA From 8752071049e94a8fc8ff5d086f24071b1210f788 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 19 May 2025 16:16:30 -0700 Subject: [PATCH 104/142] [bot] Update Python packages (#35282) Update Python packages Co-authored-by: Vehicle Researcher --- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 88 +++++++++++++++++++++++++-------------------------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/panda b/panda index 8bd83750c2..471a455789 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 8bd83750c2c0a5b8f68a00717f7516ae612fb5ab +Subproject commit 471a455789b88a0bf1098e87c49d564da266e890 diff --git a/tinygrad_repo b/tinygrad_repo index 6f77b938d7..f9a5ad24c5 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 6f77b938d77395a34f02c7d7d904d80fcd81b9cd +Subproject commit f9a5ad24c534c0fe3cb2ab8a307b2cc71efc13ea diff --git a/uv.lock b/uv.lock index cd9c84af69..0c4169e0bf 100644 --- a/uv.lock +++ b/uv.lock @@ -447,31 +447,31 @@ wheels = [ [[package]] name = "cython" -version = "3.1.0" +version = "3.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/f7/db37a613aec5abcd51c8000a386a701ac32e94659aa03fa69c3e5c19b149/cython-3.1.0.tar.gz", hash = "sha256:1097dd60d43ad0fff614a57524bfd531b35c13a907d13bee2cc2ec152e6bf4a1", size = 3181017, upload-time = "2025-05-08T20:25:36.12Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/d3/bb000603e46144db2e5055219bbddcf7ab3b10012fcb342695694fb88141/cython-3.1.1.tar.gz", hash = "sha256:505ccd413669d5132a53834d792c707974248088c4f60c497deb1b416e366397", size = 3175446, upload-time = "2025-05-19T09:44:54.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/eb20821704e0bac5051ae552db574cf5022ba658006a0c187d85d50cfd69/cython-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c736405078dc376502617eb41c39e223ae176ebd1a4ddc18179d2517bc8c8658", size = 3004811, upload-time = "2025-05-08T21:20:38.179Z" }, - { url = "https://files.pythonhosted.org/packages/59/6f/2f4e1382e365006055ec7b803372283065c4cef6f5bab88f8ed2ab350452/cython-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1215d3adb4e8691d03e712aed31206d21f387a8003d8de6a574ee75fe6d2e07c", size = 2868305, upload-time = "2025-05-08T21:20:41.1Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b3/9778e21b317c10e9a1028270eb87eda91202cd37ee1e0c09630a600629ef/cython-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:522d4dae1fea71eee5c944fb7a8530de8bdd6df0ccb2bd001d0f75be228eac6c", size = 3130151, upload-time = "2025-05-08T21:20:44.436Z" }, - { url = "https://files.pythonhosted.org/packages/58/e7/4fe8304d1caccff52a4c042274b42bf23574a380e64a86daaf4c695be510/cython-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:462ad6142057e81715ada74e2d24b9a07bf36ae3da72bf973478b5c3e809c26d", size = 3220382, upload-time = "2025-05-08T21:20:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/0a/88/9d3d7a1cb8ce3e93acbabac6f2275cfaa1021c2ddf93d2c0999bb2ee4d43/cython-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8f00cdeb14f004ebeacf946e06bad2e3ed5776af96f5af95f92d822c4ba275f", size = 3287586, upload-time = "2025-05-08T21:20:49.72Z" }, - { url = "https://files.pythonhosted.org/packages/91/49/99148b55ad1c036fb1cb69bf3729d2beecc4246f7c5f487006f16ec1f394/cython-3.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37d62b8b8919126c75769e5470b288d76c83a1645e73c7aca4b7d7aecb3c1234", size = 3175407, upload-time = "2025-05-08T21:20:51.819Z" }, - { url = "https://files.pythonhosted.org/packages/0e/52/297ce50c21647ae3741856e57f04ba69b1819e5658756823a3a1efd05e8e/cython-3.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bea0b6bfde7493acb0529fc603abd4b3b13c3bb2fff7a889ae5a8d3ea7dc5a84", size = 3381904, upload-time = "2025-05-08T21:20:53.941Z" }, - { url = "https://files.pythonhosted.org/packages/61/ab/2966d2786f4e6a7903d23b019a810675334960e8d88686a985286bd8f53d/cython-3.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fe8c1db9ec03d9ef83e33c842c108e892577ade4c5f530c9435beced048e4698", size = 3305998, upload-time = "2025-05-08T21:20:56.588Z" }, - { url = "https://files.pythonhosted.org/packages/62/52/0670d10b563b2f345477a158bd133399ddb9e7f6ff16e397e77932c07026/cython-3.1.0-cp311-cp311-win32.whl", hash = "sha256:5f6417d378bd11ca55f16e3c1c7c3bf6d7f0a0cc326c46e309fcba46c54ba4f1", size = 2454005, upload-time = "2025-05-08T21:20:58.698Z" }, - { url = "https://files.pythonhosted.org/packages/53/6e/3ea341450bc40f3c0c3da9ae05c22eeb59930531accba78a4b3eebaf342c/cython-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:dde3726aa5acbe879f849a09606b886491f950cfa993b435e50e9561fdf731c6", size = 2663258, upload-time = "2025-05-08T21:21:00.903Z" }, - { url = "https://files.pythonhosted.org/packages/e9/64/ae1d8848550ec3975634fcf189ccc85e73c3b9f76369dd85c484f2f8f1c3/cython-3.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8f8c4753f6b926046c0cdf6037ba8560f6677730bf0ab9c1db4e0163b4bb30f9", size = 3012737, upload-time = "2025-05-08T21:21:06.024Z" }, - { url = "https://files.pythonhosted.org/packages/cc/e6/c8fe5e71b4272a1b5683a006bfe4bfeb5e2691c6603b44d18eb57772f626/cython-3.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db8e15c8eeee529468eab08528c9bf714a94354b34375be6c0c110f6012a4768", size = 2843774, upload-time = "2025-05-08T21:21:08.112Z" }, - { url = "https://files.pythonhosted.org/packages/6a/eb/aeb62d32049ab55555e679b7d0cdb2f8c16f6a9d1189a56a96a0c1e86083/cython-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a46b34defa672268474fbb5541f6297f45df9e4ecc4def6edd6fe1c44bfdb795", size = 3080908, upload-time = "2025-05-08T21:21:10.228Z" }, - { url = "https://files.pythonhosted.org/packages/c9/7a/5d5507f1fafd3215ea7d01e1c78809ca71dddd88b4ce75dba3ca53ab2b3b/cython-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8818446612461aca3978ebe8e3def817a120d91f85022540843ebe4f24818cd6", size = 3197585, upload-time = "2025-05-08T21:21:12.354Z" }, - { url = "https://files.pythonhosted.org/packages/f8/97/34dd0112d042ec85736273acb2caced7e576681fbf003bcefd007bbd36d2/cython-3.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe401e825b0fbeec75f8cc758c8cf32345c673bdb0edaf9585cd43b9d2798824", size = 3244153, upload-time = "2025-05-08T21:21:14.692Z" }, - { url = "https://files.pythonhosted.org/packages/fb/98/77c50a153f4acdf7b48d055c5387edbcd3de1e0d17affca5181cb009f702/cython-3.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c96908b302e87e99915b3b66481a976e32b864e95bf054dcd2cb859dffd8cb10", size = 3123440, upload-time = "2025-05-08T21:21:16.873Z" }, - { url = "https://files.pythonhosted.org/packages/63/22/ecb9301ee1efbb0b3a6be371fd01be87d45113ed2fe804897d399da8af6f/cython-3.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cdde5f25fdb8a5d50dbe5d418fe5bfb2260b1acdbd45b788e77b247e9adf2f56", size = 3337569, upload-time = "2025-05-08T21:21:19.799Z" }, - { url = "https://files.pythonhosted.org/packages/fa/67/b707c2548cc2f790c60bb634b996f93e748ad081681c28fe7a22591e663e/cython-3.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe3320d13cde70fa8b1936e633b9e0fa68720cc61f97aa371d56d0f84fba3e02", size = 3289049, upload-time = "2025-05-08T21:21:21.997Z" }, - { url = "https://files.pythonhosted.org/packages/be/61/05b58fc96248b2d6e487f1f8d20217d3ce6d4ba5b621fb315b23c87d077c/cython-3.1.0-cp312-cp312-win32.whl", hash = "sha256:d41d17d7cfcfbddf3b7dc0ceddb6361b8e749b0b3c5f8efa40c31c249127fa15", size = 2469164, upload-time = "2025-05-08T21:21:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/4a/a1/74009b116dbec7d6c721ac268a4aa2c0c0069c99c138586f27c8fb77cae3/cython-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:61eb67401bd6c977084fc789812bd40f96be500049adb2bab99921d696ae0c87", size = 2673707, upload-time = "2025-05-08T21:21:27.716Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cc/221af506254978b119a2f6769b81b16bcfe09e0fb3fc5ab66e53e536d933/cython-3.1.0-py3-none-any.whl", hash = "sha256:4e460bdf1d8742ddf4914959842f2f23ca4934df97f864be799ddf1912acd0ab", size = 1227398, upload-time = "2025-05-08T20:25:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/35/b3/bc75c0352214b5ced31ce5e0d051d0ad4ad916aa7a1d669d1876ad1e59aa/cython-3.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c360823e1063784efc2335617e0f28573d7a594c5a8a05d85e850a9621cccb1f", size = 2998590, upload-time = "2025-05-19T09:56:51.148Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0a/5840cdd7a1e8c0d2ffeb5e09afd32b8d10321cce33a2554ef10ea832a200/cython-3.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:12e00b88147b03c148a95365f89dc1c45a0fc52f9c35aa75ff770ef65b615839", size = 2860818, upload-time = "2025-05-19T09:56:53.694Z" }, + { url = "https://files.pythonhosted.org/packages/63/2e/0fac02ce46f208af54d76c6c786b8dddeb207ca94aa85b0455f6cbaa472c/cython-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab644415458d782c16ba7252de9cec1e3125371641cafea2e53a8c1cf85dd58d", size = 3124262, upload-time = "2025-05-19T09:56:56.277Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/b7ae247b83b3f98966184c1a787001964132ae2e05a989769b1a5e7325da/cython-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5cb6c054daadaf01a88c8f49f3edd9e829c9b76a82cbb4269e3f9878254540b", size = 3215216, upload-time = "2025-05-19T09:56:58.674Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3b/c4e6c16b099a592dbd6cd615c4de901eb8cc2795d5445d77b8cd378de7da/cython-3.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af8f62cc9339b75fe8434325083e6a7cae88c9c21efd74bbb6ba4e3623219469", size = 3282597, upload-time = "2025-05-19T09:57:00.85Z" }, + { url = "https://files.pythonhosted.org/packages/06/dd/90a8fd8508298f1f16e2cbc665774047fbc81f0370125b6e32f0a182fc10/cython-3.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:689c1aad373556bd2ab1aa1c2dad8939a2891465a1fbd2cbbdd42b488fb40ec8", size = 3175592, upload-time = "2025-05-19T09:57:03.1Z" }, + { url = "https://files.pythonhosted.org/packages/af/ab/2cd4e8d5c46499ea2ca5b7c3ae4053db86465b35a8870ce5e847fee06aff/cython-3.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:953046c190fa9ab9a09a546a909b847cdbb4c1fe34e9bfa4a15b6ee1585a86aa", size = 3378435, upload-time = "2025-05-19T09:57:05.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/250fb399af2edd9861774f0c8b6c4b7d862aa52d966a07b860bcd723842a/cython-3.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:755a991601b27dd3555310d0f95b19a05e622a80d7b4e7a91fa6f5f3ef3f3b80", size = 3300519, upload-time = "2025-05-19T09:57:07.512Z" }, + { url = "https://files.pythonhosted.org/packages/37/09/1c5d470580d9b92107cadedc848f43e2f2102284f8b666ea9ab82f6fc101/cython-3.1.1-cp311-cp311-win32.whl", hash = "sha256:83b2af5c327f7da4f08afc34fddfaf6d24fa0c000b6b70a527c8125e493b6080", size = 2447287, upload-time = "2025-05-19T09:57:09.958Z" }, + { url = "https://files.pythonhosted.org/packages/30/67/c99ec81380cd9d2c798eb1572f61dbe50318958925049b39029f73fe6b52/cython-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:141ffd6279411c562f6b707adc56b63e965a4fd7f21db83f5d4fcbd8c50ac546", size = 2655739, upload-time = "2025-05-19T09:57:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/78/06/83ff82381319ff68ae46f9dd3024b1d5101997e81a8e955811525b6f934b/cython-3.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d7dc0e4d0cd491fac679a61e9ede348c64ca449f99a284f9a01851aa1dbc7f6", size = 3006334, upload-time = "2025-05-19T09:57:14.284Z" }, + { url = "https://files.pythonhosted.org/packages/c3/01/b4c46c6a27cd2da642bc987c1f9087265defbc96a1929d326b9034953f15/cython-3.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd689910002adfac8734f237cdea1573e38345f27ed7fd445482813b65a29457", size = 2836861, upload-time = "2025-05-19T09:57:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/96/51/7936c5d01ec3c89be8de1756f284878d4a567627b7b1790455ac627fb833/cython-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10f0434916994fe213ea7749268b88d77e3ebcbd1b99542cf64bb7d180f45470", size = 3074560, upload-time = "2025-05-19T09:57:18.797Z" }, + { url = "https://files.pythonhosted.org/packages/0d/81/34aeb787dcb2624a82a33e60276ed28d2da8a08c79660cf674b19be82248/cython-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:873aac4ac0b0fb197557c0ac15458b780b9221daa4a716881cbd1a9016c8459f", size = 3192645, upload-time = "2025-05-19T09:57:21.002Z" }, + { url = "https://files.pythonhosted.org/packages/e8/bf/1350ed6cb48158a4f096306a12bc4c26c6d20d3314f1f1978ea23afe0220/cython-3.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23b886a6c8a50b1101ccef2f2f3dc9c699b77633ef5bb5007090226c2ad3f9c2", size = 3241751, upload-time = "2025-05-19T09:57:23.118Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f5/9ed5a898c41723e3da2317fd1f082d963ff08571caeded31cb945be589b5/cython-3.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dff0e7dd53a0ca35b64cda843253d5cac944db26663dc097b3a1adf2c49514ad", size = 3123562, upload-time = "2025-05-19T09:57:25.492Z" }, + { url = "https://files.pythonhosted.org/packages/c3/81/b5ce4393d3a0a75a8c6d9ad0b80a62263d892260b816eb3d569ef144511a/cython-3.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f7954b0b4b3302655d3caa6924261de5907a4e129bc22ace52fe9ae0cd5a758", size = 3333555, upload-time = "2025-05-19T09:57:29.232Z" }, + { url = "https://files.pythonhosted.org/packages/db/47/2c1fa4b4901f10d00e666931dd68d4bd7954d3caa900544d135424ef6178/cython-3.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dfa500fd7ae95ca152a5f8062b870532fa3e27efcef6d00612e1f28b9f72615f", size = 3282112, upload-time = "2025-05-19T09:57:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5b/b000d3ebff79429419d846e06c5c09f33fd010e1f6158d7ba553e1082253/cython-3.1.1-cp312-cp312-win32.whl", hash = "sha256:cd748fab8e4426dbcb2e0fa2979558333934d24365e0de5672fbabfe337d880c", size = 2462293, upload-time = "2025-05-19T09:57:34.964Z" }, + { url = "https://files.pythonhosted.org/packages/45/0e/e1370ed3216e4e164232d1891c2a2932a3874d1a8681f8c3565cafd98579/cython-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:307f216ed319ea07644f2ef9974406c830f01bc8e677e2147e9bfcdf9e3ca8ad", size = 2666710, upload-time = "2025-05-19T09:57:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/a7/97/8e8637e67afc09f1b51a617b15a0d1caf0b5159b0f79d47ab101e620e491/cython-3.1.1-py3-none-any.whl", hash = "sha256:07621e044f332d18139df2ccfcc930151fd323c2f61a58c82f304cffc9eb5280", size = 1220898, upload-time = "2025-05-19T09:44:50.614Z" }, ] [[package]] @@ -4779,15 +4779,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.28.0" +version = "2.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5e/bb/6a41b2e0e9121bed4d2ec68d50568ab95c49f4744156a9bbb789c866c66d/sentry_sdk-2.28.0.tar.gz", hash = "sha256:14d2b73bc93afaf2a9412490329099e6217761cbab13b6ee8bc0e82927e1504e", size = 325052, upload-time = "2025-05-12T07:53:12.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/ec/9a313e221bb24c162f176cd7e04634f5980ce00a431befc2b2e0f78669d2/sentry_sdk-2.29.0.tar.gz", hash = "sha256:a7c6c77c46c12492c41dd70b29d481daa67cc173618c8fa60792737dbefc516b", size = 325543, upload-time = "2025-05-19T13:46:07.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/4e/b1575833094c088dfdef63fbca794518860fcbc8002aadf51ebe8b6a387f/sentry_sdk-2.28.0-py2.py3-none-any.whl", hash = "sha256:51496e6cb3cb625b99c8e08907c67a9112360259b0ef08470e532c3ab184a232", size = 341693, upload-time = "2025-05-12T07:53:10.882Z" }, + { url = "https://files.pythonhosted.org/packages/9d/cf/fa451f0518f76b21f209a71684738a3c9aa0e36ead786a5f806c3092e647/sentry_sdk-2.29.0-py2.py3-none-any.whl", hash = "sha256:5234404e2208acbce051e374f0cfc4727d69b33c5aafa5109056949b5b847c92", size = 341552, upload-time = "2025-05-19T13:46:05.067Z" }, ] [[package]] @@ -4833,29 +4833,29 @@ wheels = [ [[package]] name = "shapely" -version = "2.1.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/fe/3b0d2f828ffaceadcdcb51b75b9c62d98e62dd95ce575278de35f24a1c20/shapely-2.1.0.tar.gz", hash = "sha256:2cbe90e86fa8fc3ca8af6ffb00a77b246b918c7cf28677b7c21489b678f6b02e", size = 313617, upload-time = "2025-04-03T09:15:05.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/37/ae448f06f363ff3dfe4bae890abd842c4e3e9edaf01245dbc9b97008c9e6/shapely-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8323031ef7c1bdda7a92d5ddbc7b6b62702e73ba37e9a8ccc8da99ec2c0b87c", size = 1820974, upload-time = "2025-04-03T09:14:11.301Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/ea2a898e93c6953c5eef353a0e1781a0013a1352f2b90aa9ab0b800e0c75/shapely-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4da7c6cd748d86ec6aace99ad17129d30954ccf5e73e9911cdb5f0fa9658b4f8", size = 1624137, upload-time = "2025-04-03T09:14:13.127Z" }, - { url = "https://files.pythonhosted.org/packages/64/4a/f903f82f0fabcd3f43ea2e8132cabda079119247330a9fe58018c39c4e22/shapely-2.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f0cdf85ff80831137067e7a237085a3ee72c225dba1b30beef87f7d396cf02b", size = 2957161, upload-time = "2025-04-03T09:14:15.031Z" }, - { url = "https://files.pythonhosted.org/packages/92/07/3e2738c542d73182066196b8ce99388cb537d19e300e428d50b1537e3b21/shapely-2.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f2be5d79aac39886f23000727cf02001aef3af8810176c29ee12cdc3ef3a50", size = 3078530, upload-time = "2025-04-03T09:14:16.562Z" }, - { url = "https://files.pythonhosted.org/packages/82/08/32210e63d8f8af9142d37c2433ece4846862cdac91a0fe66f040780a71bd/shapely-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21a4515009f56d7a159cf5c2554264e82f56405b4721f9a422cb397237c5dca8", size = 3902208, upload-time = "2025-04-03T09:14:18.342Z" }, - { url = "https://files.pythonhosted.org/packages/19/0e/0abb5225f8a32fbdb615476637038a7d2db40c0af46d1bb3a08b869bee39/shapely-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cebc323cec2cb6b2eaa310fdfc621f6dbbfaf6bde336d13838fcea76c885a9", size = 4082863, upload-time = "2025-04-03T09:14:20.233Z" }, - { url = "https://files.pythonhosted.org/packages/f8/1b/7cd816fd388108c872ab7e2930180b02d0c34891213f361e4a66e5e032f2/shapely-2.1.0-cp311-cp311-win32.whl", hash = "sha256:cad51b7a5c8f82f5640472944a74f0f239123dde9a63042b3c5ea311739b7d20", size = 1527488, upload-time = "2025-04-03T09:14:21.597Z" }, - { url = "https://files.pythonhosted.org/packages/fd/28/7bb5b1944d4002d4b2f967762018500381c3b532f98e456bbda40c3ded68/shapely-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4005309dde8658e287ad9c435c81877f6a95a9419b932fa7a1f34b120f270ae", size = 1708311, upload-time = "2025-04-03T09:14:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/4e/d1/6a9371ec39d3ef08e13225594e6c55b045209629afd9e6d403204507c2a8/shapely-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53e7ee8bd8609cf12ee6dce01ea5affe676976cf7049315751d53d8db6d2b4b2", size = 1830732, upload-time = "2025-04-03T09:14:25.047Z" }, - { url = "https://files.pythonhosted.org/packages/32/87/799e3e48be7ce848c08509b94d2180f4ddb02e846e3c62d0af33da4d78d3/shapely-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cab20b665d26dbec0b380e15749bea720885a481fa7b1eedc88195d4a98cfa4", size = 1638404, upload-time = "2025-04-03T09:14:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/85/00/6665d77f9dd09478ab0993b8bc31668aec4fd3e5f1ddd1b28dd5830e47be/shapely-2.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4a38b39a09340273c3c92b3b9a374272a12cc7e468aeeea22c1c46217a03e5c", size = 2945316, upload-time = "2025-04-03T09:14:28.266Z" }, - { url = "https://files.pythonhosted.org/packages/34/49/738e07d10bbc67cae0dcfe5a484c6e518a517f4f90550dda2adf3a78b9f2/shapely-2.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edaec656bdd9b71278b98e6f77c464b1c3b2daa9eace78012ff0f0b4b5b15b04", size = 3063099, upload-time = "2025-04-03T09:14:30.067Z" }, - { url = "https://files.pythonhosted.org/packages/88/b8/138098674559362ab29f152bff3b6630de423378fbb0324812742433a4ef/shapely-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c8a732ddd9b25e7a54aa748e7df8fd704e23e5d5d35b7d376d80bffbfc376d04", size = 3887873, upload-time = "2025-04-03T09:14:31.912Z" }, - { url = "https://files.pythonhosted.org/packages/67/a8/fdae7c2db009244991d86f4d2ca09d2f5ccc9d41c312c3b1ee1404dc55da/shapely-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9c93693ad8adfdc9138a5a2d42da02da94f728dd2e82d2f0f442f10e25027f5f", size = 4067004, upload-time = "2025-04-03T09:14:33.976Z" }, - { url = "https://files.pythonhosted.org/packages/ed/78/17e17d91b489019379df3ee1afc4bd39787b232aaa1d540f7d376f0280b7/shapely-2.1.0-cp312-cp312-win32.whl", hash = "sha256:d8ac6604eefe807e71a908524de23a37920133a1729fe3a4dfe0ed82c044cbf4", size = 1527366, upload-time = "2025-04-03T09:14:35.348Z" }, - { url = "https://files.pythonhosted.org/packages/b8/bd/9249bd6dda948441e25e4fb14cbbb5205146b0fff12c66b19331f1ff2141/shapely-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f47e631aa4f9ec5576eac546eb3f38802e2f82aeb0552f9612cb9a14ece1db", size = 1708265, upload-time = "2025-04-03T09:14:36.878Z" }, + { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, + { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, + { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, + { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, + { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, + { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, + { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, + { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, + { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, + { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, ] [[package]] From f00bead198a9957c978f3622fee0aa45a9bfb483 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 20 May 2025 17:22:34 +0800 Subject: [PATCH 105/142] system/ui: improve button click logic for proper press-release interaction (#35289) improve button click logic for proper press,release interaction --- system/ui/lib/button.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 69a5da646e..4870e333df 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -41,6 +41,8 @@ BUTTON_PRESSED_BACKGROUND_COLORS = { ButtonStyle.ACTION: rl.Color(130, 130, 130, 255), } +_pressed_buttons: set[str] = set() # Track mouse press state globally + def gui_button( rect: rl.Rectangle, @@ -54,6 +56,7 @@ def gui_button( text_padding: int = 20, # Padding for left/right alignment icon=None, ) -> int: + button_id = f"{rect.x}_{rect.y}_{rect.width}_{rect.height}" result = 0 if button_style in (ButtonStyle.PRIMARY, ButtonStyle.DANGER) and not is_enabled: @@ -64,11 +67,24 @@ def gui_button( # Set background color based on button type bg_color = BUTTON_BACKGROUND_COLORS[button_style] - if is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect): - if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): + mouse_over = is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect) + + if mouse_over: + if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + # Record that this button was pressed + _pressed_buttons.add(button_id) + bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style] + elif rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style] elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): - result = 1 + # Check if this button was previously pressed + if button_id in _pressed_buttons: + result = 1 + _pressed_buttons.remove(button_id) + + # Clean up pressed state if mouse is released anywhere + if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and button_id in _pressed_buttons: + _pressed_buttons.remove(button_id) # Draw the button with rounded corners roundness = border_radius / (min(rect.width, rect.height) / 2) From f646b4a17ba4cd7e0e396cce250539ea9838eecb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 20 May 2025 17:46:56 +0800 Subject: [PATCH 106/142] system/ui: remove special currency and symbol characters from keyboard (#35291) * remove special currency and symbol characters from keyboard * bring back --------- Co-authored-by: Cameron Clough --- system/ui/widgets/keyboard.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 85608c1ef5..dfe14e7f40 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -43,7 +43,7 @@ KEYBOARD_LAYOUTS = { ], "specials": [ ["[", "]", "{", "}", "#", "%", "^", "*", "+", "="], - ["_", "\\", "|", "~", "<", ">", "€", "£", "Â¥", "•"], + ["_", "\\", "|", "~", "<", ">"], [NUMERIC_KEY, ".", ",", "?", "!", "'", BACKSPACE_KEY], [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], ], @@ -112,6 +112,10 @@ class Keyboard: new_width = (key_width * 3 + h_space * 2) if key == SPACE_KEY else (key_width * 2 + h_space if key == ENTER_KEY else key_width) key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height) + if (key_rect.x + key_rect.width) > (rect.x + rect.width): + key_rect.width = rect.x + rect.width - key_rect.x + new_width = key_rect.width + start_x += new_width is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size From cf7a295a2c0487d84f709298f6ae519a40407954 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 20 May 2025 18:01:31 +0800 Subject: [PATCH 107/142] system/ui: add key repeat functionality for backspace (#35288) add key repeat functionality for backspace Co-authored-by: Cameron Clough --- system/ui/widgets/keyboard.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index dfe14e7f40..2eda27b653 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -8,6 +8,8 @@ from openpilot.system.ui.lib.label import gui_label KEY_FONT_SIZE = 96 DOUBLE_CLICK_THRESHOLD = 0.5 # seconds +DELETE_REPEAT_DELAY = 0.5 +DELETE_REPEAT_INTERVAL = 0.07 # Constants for special keys CONTENT_MARGIN = 50 @@ -62,6 +64,11 @@ class Keyboard: self._password_mode = password_mode self._show_password_toggle = show_password_toggle + # Backspace key repeat tracking + self._backspace_pressed: bool = False + self._backspace_press_time: float = 0.0 + self._backspace_last_repeat:float = 0.0 + self._eye_open_texture = gui_app.texture("icons/eye_open.png", 81, 54) self._eye_closed_texture = gui_app.texture("icons/eye_closed.png", 81, 54) self._key_icons = { @@ -80,6 +87,7 @@ class Keyboard: self._layout_name = "lowercase" self._caps_lock = False self._input_box.clear() + self._backspace_pressed = False def render(self, title: str, sub_title: str): rect = rl.Rectangle(CONTENT_MARGIN, CONTENT_MARGIN, gui_app.width - 2 * CONTENT_MARGIN, gui_app.height - 2 * CONTENT_MARGIN) @@ -94,6 +102,21 @@ class Keyboard: input_box_rect = rl.Rectangle(rect.x + input_margin, rect.y + 160, rect.width - input_margin, 100) self._render_input_area(input_box_rect) + # Process backspace key repeat if it's held down + if not rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): + self._backspace_pressed = False + + if self._backspace_pressed: + current_time = time.monotonic() + time_since_press = current_time - self._backspace_press_time + + # After initial delay, start repeating with shorter intervals + if time_since_press > DELETE_REPEAT_DELAY: + time_since_last_repeat = current_time - self._backspace_last_repeat + if time_since_last_repeat > DELETE_REPEAT_INTERVAL: + self._input_box.delete_char_before_cursor() + self._backspace_last_repeat = current_time + layout = KEYBOARD_LAYOUTS[self._layout_name] h_space, v_space = 15, 15 @@ -120,6 +143,17 @@ class Keyboard: is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size result = -1 + + # Check for backspace key press-and-hold + mouse_pos = rl.get_mouse_position() + mouse_over_key = rl.check_collision_point_rec(mouse_pos, key_rect) + + if key == BACKSPACE_KEY and mouse_over_key: + if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): + self._backspace_pressed = True + self._backspace_press_time = time.monotonic() + self._backspace_last_repeat = time.monotonic() + if key in self._key_icons: if key == SHIFT_ACTIVE_KEY and self._caps_lock: key = CAPS_LOCK_KEY From 69799fceb4c59c5c10ba30b511408130630e82aa Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 20 May 2025 14:54:06 +0100 Subject: [PATCH 108/142] Revert "system/ui: remove special currency and symbol characters from keyboard (#35291)" This reverts commit f646b4a17ba4cd7e0e396cce250539ea9838eecb. --- system/ui/widgets/keyboard.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 2eda27b653..e940f75d2e 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -45,7 +45,7 @@ KEYBOARD_LAYOUTS = { ], "specials": [ ["[", "]", "{", "}", "#", "%", "^", "*", "+", "="], - ["_", "\\", "|", "~", "<", ">"], + ["_", "\\", "|", "~", "<", ">", "€", "£", "Â¥", "•"], [NUMERIC_KEY, ".", ",", "?", "!", "'", BACKSPACE_KEY], [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], ], @@ -135,10 +135,6 @@ class Keyboard: new_width = (key_width * 3 + h_space * 2) if key == SPACE_KEY else (key_width * 2 + h_space if key == ENTER_KEY else key_width) key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height) - if (key_rect.x + key_rect.width) > (rect.x + rect.width): - key_rect.width = rect.x + rect.width - key_rect.x - new_width = key_rect.width - start_x += new_width is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size From 566758319828471fc3d2c541957dea22f1e224c1 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 20 May 2025 15:47:10 +0100 Subject: [PATCH 109/142] system/ui: define character set for loading fonts (#35298) * system/ui: define font character set * remove debug print statement * shorter --- system/ui/lib/application.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8639a0bda5..4d7ba6d24e 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -190,12 +190,23 @@ class GuiApplication: "Inter-Black.ttf", ) + # Create a character set from our keyboard layouts + from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS + all_chars = "" + for layout in KEYBOARD_LAYOUTS.values(): + all_chars.update(key for row in layout for key in row) + all_chars = "".join(all_chars) + + codepoint_count = rl.ffi.new("int *", 1) + codepoints = rl.load_codepoints(all_chars, codepoint_count) + for index, font_file in enumerate(font_files): with as_file(FONT_DIR.joinpath(font_file)) as fspath: - font = rl.load_font_ex(fspath.as_posix(), 120, None, 0) + font = rl.load_font_ex(fspath.as_posix(), 120, codepoints, codepoint_count[0]) rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) self._fonts[index] = font + rl.unload_codepoints(codepoints) rl.gui_set_font(self._fonts[FontWeight.NORMAL]) def _set_styles(self): From 2722e12e6252049858dd1b3efbd98cb5d064a625 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 20 May 2025 15:48:44 +0100 Subject: [PATCH 110/142] fix(system/ui): typo --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4d7ba6d24e..37da413806 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -192,7 +192,7 @@ class GuiApplication: # Create a character set from our keyboard layouts from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS - all_chars = "" + all_chars = set() for layout in KEYBOARD_LAYOUTS.values(): all_chars.update(key for row in layout for key in row) all_chars = "".join(all_chars) From beb7f6c2bb80ce53470fd1f88c151c248a643b45 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 21 May 2025 02:23:05 +0800 Subject: [PATCH 111/142] system/ui: fix indentation (#35299) fix indentation --- system/ui/widgets/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index e940f75d2e..7836b28184 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -140,7 +140,7 @@ class Keyboard: is_enabled = key != ENTER_KEY or len(self._input_box.text) >= self._min_text_size result = -1 - # Check for backspace key press-and-hold + # Check for backspace key press-and-hold mouse_pos = rl.get_mouse_position() mouse_over_key = rl.check_collision_point_rec(mouse_pos, key_rect) From 4f512a5c6d9d103e024cb87f724d6f9ccf6f65cf Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 21 May 2025 03:27:08 +0800 Subject: [PATCH 112/142] system/ui: fix navigation key repeat in input box (#35302) fix navigation key repeat in input fields --- system/ui/lib/inputbox.py | 44 ++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index 8e53446b43..78ee03bdbd 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -13,7 +13,7 @@ class InputBox: self._last_key_pressed = 0 self._key_press_time = 0 self._repeat_delay = 30 - self._repeat_rate = 5 + self._repeat_rate = 4 @property def text(self): @@ -119,22 +119,29 @@ class InputBox: self.set_cursor_position(0) def _handle_keyboard_input(self): - """Process keyboard input.""" - key = rl.get_key_pressed() - - # Handle key repeats - if key == self._last_key_pressed and key != 0: - self._key_press_time += 1 - if self._key_press_time > self._repeat_delay and self._key_press_time % self._repeat_rate == 0: - # Process repeated key - pass - else: - return # Skip processing until repeat triggers - else: - self._last_key_pressed = key - self._key_press_time = 0 - # Handle navigation keys + key = rl.get_key_pressed() + if key != 0: + self._process_key(key) + if key in (rl.KEY_LEFT, rl.KEY_RIGHT, rl.KEY_BACKSPACE, rl.KEY_DELETE): + self._last_key_pressed = key + self._key_press_time = 0 + + # Handle repeats for held keys + elif self._last_key_pressed != 0: + if rl.is_key_down(self._last_key_pressed): + self._key_press_time += 1 + if self._key_press_time > self._repeat_delay and self._key_press_time % self._repeat_rate == 0: + self._process_key(self._last_key_pressed) + else: + self._last_key_pressed = 0 + + # Handle text input + char = rl.get_char_pressed() + if char != 0 and char >= 32: # Filter out control characters + self.add_char_at_cursor(chr(char)) + + def _process_key(self, key): if key == rl.KEY_LEFT: if self._cursor_position > 0: self.set_cursor_position(self._cursor_position - 1) @@ -149,8 +156,3 @@ class InputBox: self.set_cursor_position(0) elif key == rl.KEY_END: self.set_cursor_position(len(self._input_text)) - - # Handle text input - char = rl.get_char_pressed() - if char != 0 and char >= 32: # Filter out control characters - self.add_char_at_cursor(chr(char)) From e47d105b97c0f41812bc892e8f4157dcb48a5770 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 20 May 2025 14:28:30 -0700 Subject: [PATCH 113/142] revert extra GPU power draw to prep for release --- selfdrive/modeld/dmonitoringmodeld.py | 3 --- system/hardware/tici/tests/test_power_draw.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 94e117bc6a..2a3df3f652 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -173,9 +173,6 @@ def main(): model_output, gpu_execution_time = model.run(buf, calib, model_transform) t2 = time.perf_counter() - # run one more time, just for the load - model.run(buf, calib, model_transform) - pm.send("driverStateV2", get_driverstate_packet(model_output, vipc_client.frame_id, vipc_client.timestamp_sof, t2 - t1, gpu_execution_time)) diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index fd65ecd5cd..db0fab884c 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -33,7 +33,7 @@ class Proc: PROCS = [ Proc(['camerad'], 1.75, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), Proc(['modeld'], 1.12, atol=0.2, msgs=['modelV2']), - Proc(['dmonitoringmodeld'], 1.4, msgs=['driverStateV2']), + Proc(['dmonitoringmodeld'], 0.6, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] From f5cf062a38d722d0f69dc9b5c66f58fd9c110d9c Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Tue, 20 May 2025 14:57:13 -0700 Subject: [PATCH 114/142] [bot] Update Python packages (#35303) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 86 +++++++++++++++++++++++++-------------------------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 447381dee2..0676642f40 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 447381dee2d30fb46e4bf1aec0529667d0b9d159 +Subproject commit 0676642f4029b02758405b3052d1ef8ad2a5528d diff --git a/panda b/panda index 471a455789..871f065e97 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 471a455789b88a0bf1098e87c49d564da266e890 +Subproject commit 871f065e97caf2d85c909d69f38939a79d8563b6 diff --git a/tinygrad_repo b/tinygrad_repo index f9a5ad24c5..2895198c36 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit f9a5ad24c534c0fe3cb2ab8a307b2cc71efc13ea +Subproject commit 2895198c368e226e2176aae37d5e545691ce85cb diff --git a/uv.lock b/uv.lock index 0c4169e0bf..2f8cf68094 100644 --- a/uv.lock +++ b/uv.lock @@ -1103,45 +1103,45 @@ wheels = [ [[package]] name = "multidict" -version = "6.4.3" +version = "6.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/2c/e367dfb4c6538614a0c9453e510d75d66099edf1c4e69da1b5ce691a1931/multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec", size = 89372, upload-time = "2025-04-10T22:20:17.956Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/2f/a3470242707058fe856fe59241eee5635d79087100b7042a867368863a27/multidict-6.4.4.tar.gz", hash = "sha256:69ee9e6ba214b5245031b76233dd95408a0fd57fdb019ddcc1ead4790932a8e8", size = 90183, upload-time = "2025-05-19T14:16:37.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e0/53cf7f27eda48fffa53cfd4502329ed29e00efb9e4ce41362cbf8aa54310/multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd", size = 65259, upload-time = "2025-04-10T22:17:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/44/79/1dcd93ce7070cf01c2ee29f781c42b33c64fce20033808f1cc9ec8413d6e/multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8", size = 38451, upload-time = "2025-04-10T22:18:01.202Z" }, - { url = "https://files.pythonhosted.org/packages/f4/35/2292cf29ab5f0d0b3613fad1b75692148959d3834d806be1885ceb49a8ff/multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad", size = 37706, upload-time = "2025-04-10T22:18:02.276Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d1/6b157110b2b187b5a608b37714acb15ee89ec773e3800315b0107ea648cd/multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852", size = 226669, upload-time = "2025-04-10T22:18:03.436Z" }, - { url = "https://files.pythonhosted.org/packages/40/7f/61a476450651f177c5570e04bd55947f693077ba7804fe9717ee9ae8de04/multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08", size = 223182, upload-time = "2025-04-10T22:18:04.922Z" }, - { url = "https://files.pythonhosted.org/packages/51/7b/eaf7502ac4824cdd8edcf5723e2e99f390c879866aec7b0c420267b53749/multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229", size = 235025, upload-time = "2025-04-10T22:18:06.274Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f6/facdbbd73c96b67a93652774edd5778ab1167854fa08ea35ad004b1b70ad/multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508", size = 231481, upload-time = "2025-04-10T22:18:07.742Z" }, - { url = "https://files.pythonhosted.org/packages/70/57/c008e861b3052405eebf921fd56a748322d8c44dcfcab164fffbccbdcdc4/multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7", size = 223492, upload-time = "2025-04-10T22:18:09.095Z" }, - { url = "https://files.pythonhosted.org/packages/30/4d/7d8440d3a12a6ae5d6b202d6e7f2ac6ab026e04e99aaf1b73f18e6bc34bc/multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8", size = 217279, upload-time = "2025-04-10T22:18:10.474Z" }, - { url = "https://files.pythonhosted.org/packages/7f/e7/bca0df4dd057597b94138d2d8af04eb3c27396a425b1b0a52e082f9be621/multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56", size = 228733, upload-time = "2025-04-10T22:18:11.793Z" }, - { url = "https://files.pythonhosted.org/packages/88/f5/383827c3f1c38d7c92dbad00a8a041760228573b1c542fbf245c37bbca8a/multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0", size = 218089, upload-time = "2025-04-10T22:18:13.153Z" }, - { url = "https://files.pythonhosted.org/packages/36/8a/a5174e8a7d8b94b4c8f9c1e2cf5d07451f41368ffe94d05fc957215b8e72/multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777", size = 225257, upload-time = "2025-04-10T22:18:14.654Z" }, - { url = "https://files.pythonhosted.org/packages/8c/76/1d4b7218f0fd00b8e5c90b88df2e45f8af127f652f4e41add947fa54c1c4/multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2", size = 234728, upload-time = "2025-04-10T22:18:16.236Z" }, - { url = "https://files.pythonhosted.org/packages/64/44/18372a4f6273fc7ca25630d7bf9ae288cde64f29593a078bff450c7170b6/multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618", size = 230087, upload-time = "2025-04-10T22:18:17.979Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/28728c314a698d8a6d9491fcacc897077348ec28dd85884d09e64df8a855/multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7", size = 223137, upload-time = "2025-04-10T22:18:19.362Z" }, - { url = "https://files.pythonhosted.org/packages/22/50/785bb2b3fe16051bc91c70a06a919f26312da45c34db97fc87441d61e343/multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378", size = 34959, upload-time = "2025-04-10T22:18:20.728Z" }, - { url = "https://files.pythonhosted.org/packages/2f/63/2a22e099ae2f4d92897618c00c73a09a08a2a9aa14b12736965bf8d59fd3/multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589", size = 38541, upload-time = "2025-04-10T22:18:22.001Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bb/3abdaf8fe40e9226ce8a2ba5ecf332461f7beec478a455d6587159f1bf92/multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676", size = 64019, upload-time = "2025-04-10T22:18:23.174Z" }, - { url = "https://files.pythonhosted.org/packages/7e/b5/1b2e8de8217d2e89db156625aa0fe4a6faad98972bfe07a7b8c10ef5dd6b/multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1", size = 37925, upload-time = "2025-04-10T22:18:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e2/3ca91c112644a395c8eae017144c907d173ea910c913ff8b62549dcf0bbf/multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a", size = 37008, upload-time = "2025-04-10T22:18:26.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/23/79bc78146c7ac8d1ac766b2770ca2e07c2816058b8a3d5da6caed8148637/multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054", size = 224374, upload-time = "2025-04-10T22:18:27.714Z" }, - { url = "https://files.pythonhosted.org/packages/86/35/77950ed9ebd09136003a85c1926ba42001ca5be14feb49710e4334ee199b/multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc", size = 230869, upload-time = "2025-04-10T22:18:29.162Z" }, - { url = "https://files.pythonhosted.org/packages/49/97/2a33c6e7d90bc116c636c14b2abab93d6521c0c052d24bfcc231cbf7f0e7/multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07", size = 231949, upload-time = "2025-04-10T22:18:30.679Z" }, - { url = "https://files.pythonhosted.org/packages/56/ce/e9b5d9fcf854f61d6686ada7ff64893a7a5523b2a07da6f1265eaaea5151/multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde", size = 231032, upload-time = "2025-04-10T22:18:32.146Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ac/7ced59dcdfeddd03e601edb05adff0c66d81ed4a5160c443e44f2379eef0/multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c", size = 223517, upload-time = "2025-04-10T22:18:33.538Z" }, - { url = "https://files.pythonhosted.org/packages/db/e6/325ed9055ae4e085315193a1b58bdb4d7fc38ffcc1f4975cfca97d015e17/multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae", size = 216291, upload-time = "2025-04-10T22:18:34.962Z" }, - { url = "https://files.pythonhosted.org/packages/fa/84/eeee6d477dd9dcb7691c3bb9d08df56017f5dd15c730bcc9383dcf201cf4/multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3", size = 228982, upload-time = "2025-04-10T22:18:36.443Z" }, - { url = "https://files.pythonhosted.org/packages/82/94/4d1f3e74e7acf8b0c85db350e012dcc61701cd6668bc2440bb1ecb423c90/multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507", size = 226823, upload-time = "2025-04-10T22:18:37.924Z" }, - { url = "https://files.pythonhosted.org/packages/09/f0/1e54b95bda7cd01080e5732f9abb7b76ab5cc795b66605877caeb2197476/multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427", size = 222714, upload-time = "2025-04-10T22:18:39.807Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a2/f6cbca875195bd65a3e53b37ab46486f3cc125bdeab20eefe5042afa31fb/multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731", size = 233739, upload-time = "2025-04-10T22:18:41.341Z" }, - { url = "https://files.pythonhosted.org/packages/79/68/9891f4d2b8569554723ddd6154375295f789dc65809826c6fb96a06314fd/multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713", size = 230809, upload-time = "2025-04-10T22:18:42.817Z" }, - { url = "https://files.pythonhosted.org/packages/e6/72/a7be29ba1e87e4fc5ceb44dabc7940b8005fd2436a332a23547709315f70/multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a", size = 226934, upload-time = "2025-04-10T22:18:44.311Z" }, - { url = "https://files.pythonhosted.org/packages/12/c1/259386a9ad6840ff7afc686da96808b503d152ac4feb3a96c651dc4f5abf/multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124", size = 35242, upload-time = "2025-04-10T22:18:46.193Z" }, - { url = "https://files.pythonhosted.org/packages/06/24/c8fdff4f924d37225dc0c56a28b1dca10728fc2233065fafeb27b4b125be/multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db", size = 38635, upload-time = "2025-04-10T22:18:47.498Z" }, - { url = "https://files.pythonhosted.org/packages/96/10/7d526c8974f017f1e7ca584c71ee62a638e9334d8d33f27d7cdfc9ae79e4/multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9", size = 10400, upload-time = "2025-04-10T22:20:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/1b/4c6e638195851524a63972c5773c7737bea7e47b1ba402186a37773acee2/multidict-6.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f5f29794ac0e73d2a06ac03fd18870adc0135a9d384f4a306a951188ed02f95", size = 65515, upload-time = "2025-05-19T14:14:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/25/d5/10e6bca9a44b8af3c7f920743e5fc0c2bcf8c11bf7a295d4cfe00b08fb46/multidict-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c04157266344158ebd57b7120d9b0b35812285d26d0e78193e17ef57bfe2979a", size = 38609, upload-time = "2025-05-19T14:14:21.538Z" }, + { url = "https://files.pythonhosted.org/packages/26/b4/91fead447ccff56247edc7f0535fbf140733ae25187a33621771ee598a18/multidict-6.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bb61ffd3ab8310d93427e460f565322c44ef12769f51f77277b4abad7b6f7223", size = 37871, upload-time = "2025-05-19T14:14:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/3b/37/cbc977cae59277e99d15bbda84cc53b5e0c4929ffd91d958347200a42ad0/multidict-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e0ba18a9afd495f17c351d08ebbc4284e9c9f7971d715f196b79636a4d0de44", size = 226661, upload-time = "2025-05-19T14:14:24.124Z" }, + { url = "https://files.pythonhosted.org/packages/15/cd/7e0b57fbd4dc2fc105169c4ecce5be1a63970f23bb4ec8c721b67e11953d/multidict-6.4.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9faf1b1dcaadf9f900d23a0e6d6c8eadd6a95795a0e57fcca73acce0eb912065", size = 223422, upload-time = "2025-05-19T14:14:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/1de268da121bac9f93242e30cd3286f6a819e5f0b8896511162d6ed4bf8d/multidict-6.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4d1cb1327c6082c4fce4e2a438483390964c02213bc6b8d782cf782c9b1471f", size = 235447, upload-time = "2025-05-19T14:14:26.793Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8c/8b9a5e4aaaf4f2de14e86181a3a3d7b105077f668b6a06f043ec794f684c/multidict-6.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:941f1bec2f5dbd51feeb40aea654c2747f811ab01bdd3422a48a4e4576b7d76a", size = 231455, upload-time = "2025-05-19T14:14:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/35/db/e1817dcbaa10b319c412769cf999b1016890849245d38905b73e9c286862/multidict-6.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5f8a146184da7ea12910a4cec51ef85e44f6268467fb489c3caf0cd512f29c2", size = 223666, upload-time = "2025-05-19T14:14:29.584Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e1/66e8579290ade8a00e0126b3d9a93029033ffd84f0e697d457ed1814d0fc/multidict-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:232b7237e57ec3c09be97206bfb83a0aa1c5d7d377faa019c68a210fa35831f1", size = 217392, upload-time = "2025-05-19T14:14:30.961Z" }, + { url = "https://files.pythonhosted.org/packages/7b/6f/f8639326069c24a48c7747c2a5485d37847e142a3f741ff3340c88060a9a/multidict-6.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:55ae0721c1513e5e3210bca4fc98456b980b0c2c016679d3d723119b6b202c42", size = 228969, upload-time = "2025-05-19T14:14:32.672Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c3/3d58182f76b960eeade51c89fcdce450f93379340457a328e132e2f8f9ed/multidict-6.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:51d662c072579f63137919d7bb8fc250655ce79f00c82ecf11cab678f335062e", size = 217433, upload-time = "2025-05-19T14:14:34.016Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4b/f31a562906f3bd375f3d0e83ce314e4a660c01b16c2923e8229b53fba5d7/multidict-6.4.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0e05c39962baa0bb19a6b210e9b1422c35c093b651d64246b6c2e1a7e242d9fd", size = 225418, upload-time = "2025-05-19T14:14:35.376Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/78bb95c89c496d64b5798434a3deee21996114d4d2c28dd65850bf3a691e/multidict-6.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5b1cc3ab8c31d9ebf0faa6e3540fb91257590da330ffe6d2393d4208e638925", size = 235042, upload-time = "2025-05-19T14:14:36.723Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/8780a6e5885a8770442a8f80db86a0887c4becca0e5a2282ba2cae702bc4/multidict-6.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:93ec84488a384cd7b8a29c2c7f467137d8a73f6fe38bb810ecf29d1ade011a7c", size = 230280, upload-time = "2025-05-19T14:14:38.194Z" }, + { url = "https://files.pythonhosted.org/packages/68/c1/fcf69cabd542eb6f4b892469e033567ee6991d361d77abdc55e3a0f48349/multidict-6.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b308402608493638763abc95f9dc0030bbd6ac6aff784512e8ac3da73a88af08", size = 223322, upload-time = "2025-05-19T14:14:40.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/85/5b80bf4b83d8141bd763e1d99142a9cdfd0db83f0739b4797172a4508014/multidict-6.4.4-cp311-cp311-win32.whl", hash = "sha256:343892a27d1a04d6ae455ecece12904d242d299ada01633d94c4f431d68a8c49", size = 35070, upload-time = "2025-05-19T14:14:41.904Z" }, + { url = "https://files.pythonhosted.org/packages/09/66/0bed198ffd590ab86e001f7fa46b740d58cf8ff98c2f254e4a36bf8861ad/multidict-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:73484a94f55359780c0f458bbd3c39cb9cf9c182552177d2136e828269dee529", size = 38667, upload-time = "2025-05-19T14:14:43.534Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/5675377da23d60875fe7dae6be841787755878e315e2f517235f22f59e18/multidict-6.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc388f75a1c00000824bf28b7633e40854f4127ede80512b44c3cfeeea1839a2", size = 64293, upload-time = "2025-05-19T14:14:44.724Z" }, + { url = "https://files.pythonhosted.org/packages/34/a7/be384a482754bb8c95d2bbe91717bf7ccce6dc38c18569997a11f95aa554/multidict-6.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:98af87593a666f739d9dba5d0ae86e01b0e1a9cfcd2e30d2d361fbbbd1a9162d", size = 38096, upload-time = "2025-05-19T14:14:45.95Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/d59854bb4352306145bdfd1704d210731c1bb2c890bfee31fb7bbc1c4c7f/multidict-6.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aff4cafea2d120327d55eadd6b7f1136a8e5a0ecf6fb3b6863e8aca32cd8e50a", size = 37214, upload-time = "2025-05-19T14:14:47.158Z" }, + { url = "https://files.pythonhosted.org/packages/99/e0/c29d9d462d7cfc5fc8f9bf24f9c6843b40e953c0b55e04eba2ad2cf54fba/multidict-6.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169c4ba7858176b797fe551d6e99040c531c775d2d57b31bcf4de6d7a669847f", size = 224686, upload-time = "2025-05-19T14:14:48.366Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4a/da99398d7fd8210d9de068f9a1b5f96dfaf67d51e3f2521f17cba4ee1012/multidict-6.4.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9eb4c59c54421a32b3273d4239865cb14ead53a606db066d7130ac80cc8ec93", size = 231061, upload-time = "2025-05-19T14:14:49.952Z" }, + { url = "https://files.pythonhosted.org/packages/21/f5/ac11add39a0f447ac89353e6ca46666847051103649831c08a2800a14455/multidict-6.4.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cf3bd54c56aa16fdb40028d545eaa8d051402b61533c21e84046e05513d5780", size = 232412, upload-time = "2025-05-19T14:14:51.812Z" }, + { url = "https://files.pythonhosted.org/packages/d9/11/4b551e2110cded705a3c13a1d4b6a11f73891eb5a1c449f1b2b6259e58a6/multidict-6.4.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f682c42003c7264134bfe886376299db4cc0c6cd06a3295b41b347044bcb5482", size = 231563, upload-time = "2025-05-19T14:14:53.262Z" }, + { url = "https://files.pythonhosted.org/packages/4c/02/751530c19e78fe73b24c3da66618eda0aa0d7f6e7aa512e46483de6be210/multidict-6.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920f9cf2abdf6e493c519492d892c362007f113c94da4c239ae88429835bad1", size = 223811, upload-time = "2025-05-19T14:14:55.232Z" }, + { url = "https://files.pythonhosted.org/packages/c7/cb/2be8a214643056289e51ca356026c7b2ce7225373e7a1f8c8715efee8988/multidict-6.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:530d86827a2df6504526106b4c104ba19044594f8722d3e87714e847c74a0275", size = 216524, upload-time = "2025-05-19T14:14:57.226Z" }, + { url = "https://files.pythonhosted.org/packages/19/f3/6d5011ec375c09081f5250af58de85f172bfcaafebff286d8089243c4bd4/multidict-6.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ecde56ea2439b96ed8a8d826b50c57364612ddac0438c39e473fafad7ae1c23b", size = 229012, upload-time = "2025-05-19T14:14:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/67/9c/ca510785df5cf0eaf5b2a8132d7d04c1ce058dcf2c16233e596ce37a7f8e/multidict-6.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:dc8c9736d8574b560634775ac0def6bdc1661fc63fa27ffdfc7264c565bcb4f2", size = 226765, upload-time = "2025-05-19T14:15:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/ca86019994e92a0f11e642bda31265854e6ea7b235642f0477e8c2e25c1f/multidict-6.4.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7f3d3b3c34867579ea47cbd6c1f2ce23fbfd20a273b6f9e3177e256584f1eacc", size = 222888, upload-time = "2025-05-19T14:15:01.568Z" }, + { url = "https://files.pythonhosted.org/packages/c6/67/bc25a8e8bd522935379066950ec4e2277f9b236162a73548a2576d4b9587/multidict-6.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:87a728af265e08f96b6318ebe3c0f68b9335131f461efab2fc64cc84a44aa6ed", size = 234041, upload-time = "2025-05-19T14:15:03.759Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a0/70c4c2d12857fccbe607b334b7ee28b6b5326c322ca8f73ee54e70d76484/multidict-6.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9f193eeda1857f8e8d3079a4abd258f42ef4a4bc87388452ed1e1c4d2b0c8740", size = 231046, upload-time = "2025-05-19T14:15:05.698Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/52954601d02d39742aab01d6b92f53c1dd38b2392248154c50797b4df7f1/multidict-6.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be06e73c06415199200e9a2324a11252a3d62030319919cde5e6950ffeccf72e", size = 227106, upload-time = "2025-05-19T14:15:07.124Z" }, + { url = "https://files.pythonhosted.org/packages/af/24/679d83ec4379402d28721790dce818e5d6b9f94ce1323a556fb17fa9996c/multidict-6.4.4-cp312-cp312-win32.whl", hash = "sha256:622f26ea6a7e19b7c48dd9228071f571b2fbbd57a8cd71c061e848f281550e6b", size = 35351, upload-time = "2025-05-19T14:15:08.556Z" }, + { url = "https://files.pythonhosted.org/packages/52/ef/40d98bc5f986f61565f9b345f102409534e29da86a6454eb6b7c00225a13/multidict-6.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:5e2bcda30d5009996ff439e02a9f2b5c3d64a20151d34898c000a6281faa3781", size = 38791, upload-time = "2025-05-19T14:15:09.825Z" }, + { url = "https://files.pythonhosted.org/packages/84/5d/e17845bb0fa76334477d5de38654d27946d5b5d3695443987a094a71b440/multidict-6.4.4-py3-none-any.whl", hash = "sha256:bd4557071b561a8b3b6075c3ce93cf9bfb6182cb241805c3d66ced3b75eff4ac", size = 10481, upload-time = "2025-05-19T14:16:36.024Z" }, ] [[package]] @@ -4779,15 +4779,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.29.0" +version = "2.29.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/ec/9a313e221bb24c162f176cd7e04634f5980ce00a431befc2b2e0f78669d2/sentry_sdk-2.29.0.tar.gz", hash = "sha256:a7c6c77c46c12492c41dd70b29d481daa67cc173618c8fa60792737dbefc516b", size = 325543, upload-time = "2025-05-19T13:46:07.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/67/d552a5f8e5a6a56b2feea6529e2d8ccd54349084c84176d5a1f7295044bc/sentry_sdk-2.29.1.tar.gz", hash = "sha256:8d4a0206b95fa5fe85e5e7517ed662e3888374bdc342c00e435e10e6d831aa6d", size = 325518, upload-time = "2025-05-19T14:27:38.512Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/cf/fa451f0518f76b21f209a71684738a3c9aa0e36ead786a5f806c3092e647/sentry_sdk-2.29.0-py2.py3-none-any.whl", hash = "sha256:5234404e2208acbce051e374f0cfc4727d69b33c5aafa5109056949b5b847c92", size = 341552, upload-time = "2025-05-19T13:46:05.067Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e5/da07b0bd832cefd52d16f2b9bbbe31624d57552602c06631686b93ccb1bd/sentry_sdk-2.29.1-py2.py3-none-any.whl", hash = "sha256:90862fe0616ded4572da6c9dadb363121a1ae49a49e21c418f0634e9d10b4c19", size = 341553, upload-time = "2025-05-19T14:27:36.882Z" }, ] [[package]] @@ -4824,11 +4824,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.7.1" +version = "80.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188, upload-time = "2025-05-15T02:41:00.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/d2/ec1acaaff45caed5c2dedb33b67055ba9d4e96b091094df90762e60135fe/setuptools-80.8.0.tar.gz", hash = "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257", size = 1319720, upload-time = "2025-05-20T14:02:53.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776, upload-time = "2025-05-15T02:40:58.887Z" }, + { url = "https://files.pythonhosted.org/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0", size = 1201470, upload-time = "2025-05-20T14:02:51.348Z" }, ] [[package]] From ac9222bff48cc10f71aba95fc7bfc3a979dfcf7e Mon Sep 17 00:00:00 2001 From: Ocheretovich Date: Wed, 21 May 2025 00:57:26 +0300 Subject: [PATCH 115/142] docs: added a link to the selfdrive badge (#35300) Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b0248b0f54..86cccafad9 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Quick start: `bash <(curl -fsSL openpilot.comma.ai)` -![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml/badge.svg) +[![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml/badge.svg)](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml) [![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![X Follow](https://img.shields.io/twitter/follow/comma_ai)](https://x.com/comma_ai) From 10fd0f9a5725bd9a7d514c142a8e0232d3480688 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Tue, 20 May 2025 16:45:33 -0700 Subject: [PATCH 116/142] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 871f065e97..d75699e6ab 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 871f065e97caf2d85c909d69f38939a79d8563b6 +Subproject commit d75699e6abf41636701775ba0eea8e07858599ad From 9cd939d35497bc6d96607d4f5de3441501facd6f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 21 May 2025 07:59:48 +0800 Subject: [PATCH 117/142] ui: rewrite installer using raylib, remove qt (#33756) * update SConscript Co-authored-by: Dean Lee * can't build on mac * raylib installer Co-authored-by: Dean Lee * rm * debug pls * cleanup * quotes * libs? * bump * hmm * progress bar color * more * not rounded * hardcode a font path..? * embed (a subset of) inter into the installer * different in CI * closer font sizes * closer * add that back * unnecessary * closer to previous impl * prefix is at start of string, substr between prefix and % * Revert "prefix is at start of string, substr between prefix and %" This reverts commit bc53fe8e356ca642680e90682285bd5e8d98ecb5. * bigger on device? * rm --------- Co-authored-by: Cameron Clough --- selfdrive/ui/SConscript | 26 ++-- selfdrive/ui/installer/installer.cc | 182 +++++++++++-------------- selfdrive/ui/installer/installer.h | 28 ---- selfdrive/ui/installer/inter-ascii.ttf | 3 + 4 files changed, 97 insertions(+), 142 deletions(-) delete mode 100644 selfdrive/ui/installer/installer.h create mode 100644 selfdrive/ui/installer/inter-ascii.ttf diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index e63359da05..7199399d41 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,5 @@ -import os import json -Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -76,8 +75,15 @@ if GetOption('extras'): if arch != "Darwin": # build installers - senv = qt_env.Clone() - senv['LINKFLAGS'].append('-Wl,-strip-debug') + raylib_env = env.Clone() + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "wayland-client", "wayland-egl", "EGL"] + else: + raylib_libs += ["GL"] release = "release3" installers = [ @@ -87,17 +93,19 @@ if GetOption('extras'): ("openpilot_internal", "nightly-dev"), ] - cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh", - "ld -r -b binary -o $TARGET $SOURCE") + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") for name, branch in installers: d = {'BRANCH': f"'\"{branch}\"'"} if "internal" in name: d['INTERNAL'] = "1" - obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs) + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) # keep installers small - assert f[0].get_size() < 370*1e3 + assert f[0].get_size() < 1300*1e3, f[0].get_size() # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 84486e61be..7326e089ab 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -1,19 +1,15 @@ -#include - -#include +#include +#include #include #include -#include - -#include -#include -#include -#include +#include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/ui/installer/installer.h" -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/qt_window.h" +#include "third_party/raylib/include/raylib.h" + +int freshClone(); +int cachedFetch(const std::string &cache); +int executeGitCommand(const std::string &cmd); std::string get_str(std::string const s) { std::string::size_type pos = s.find('?'); @@ -28,136 +24,108 @@ const std::string BRANCH_STR = get_str(BRANCH "? #define GIT_SSH_URL "git@github.com:commaai/openpilot.git" #define CONTINUE_PATH "/data/continue.sh" -const QString CACHE_PATH = "/data/openpilot.cache"; +const std::string CACHE_PATH = "/data/openpilot.cache"; #define INSTALL_PATH "/data/openpilot" #define TMP_INSTALL_PATH "/data/tmppilot" extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); +extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start"); +extern const uint8_t inter_ttf_end[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_end"); + +Font font; void run(const char* cmd) { int err = std::system(cmd); assert(err == 0); } -Installer::Installer(QWidget *parent) : QWidget(parent) { - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setContentsMargins(150, 290, 150, 150); - layout->setSpacing(0); - - QLabel *title = new QLabel(tr("Installing...")); - title->setStyleSheet("font-size: 90px; font-weight: 600;"); - layout->addWidget(title, 0, Qt::AlignTop); - - layout->addSpacing(170); - - bar = new QProgressBar(); - bar->setRange(0, 100); - bar->setTextVisible(false); - bar->setFixedHeight(72); - layout->addWidget(bar, 0, Qt::AlignTop); - - layout->addSpacing(30); - - val = new QLabel("0%"); - val->setStyleSheet("font-size: 70px; font-weight: 300;"); - layout->addWidget(val, 0, Qt::AlignTop); - - layout->addStretch(); - - QObject::connect(&proc, QOverload::of(&QProcess::finished), this, &Installer::cloneFinished); - QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Installer::readProgress); - - QTimer::singleShot(100, this, &Installer::doInstall); - - setStyleSheet(R"( - * { - font-family: Inter; - color: white; - background-color: black; - } - QProgressBar { - border: none; - background-color: #292929; - } - QProgressBar::chunk { - background-color: #364DEF; - } - )"); +void renderProgress(int progress) { + BeginDrawing(); + ClearBackground(BLACK); + DrawTextEx(font, "Installing...", (Vector2){150, 290}, 110, 0, WHITE); + Rectangle bar = {150, 570, (float)GetScreenWidth() - 300, 72}; + DrawRectangleRec(bar, (Color){41, 41, 41, 255}); + progress = std::clamp(progress, 0, 100); + bar.width *= progress / 100.0f; + DrawRectangleRec(bar, (Color){70, 91, 234, 255}); + DrawTextEx(font, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); + EndDrawing(); } -void Installer::updateProgress(int percent) { - bar->setValue(percent); - val->setText(QString("%1%").arg(percent)); - update(); -} - -void Installer::doInstall() { +int doInstall() { // wait for valid time while (!util::system_time_valid()) { - usleep(500 * 1000); - qDebug() << "Waiting for valid time"; + util::sleep_for(500); + LOGD("Waiting for valid time"); } // cleanup previous install attempts run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH); // do the install - if (QDir(CACHE_PATH).exists()) { - cachedFetch(CACHE_PATH); + if (util::file_exists(CACHE_PATH)) { + return cachedFetch(CACHE_PATH); } else { - freshClone(); + return freshClone(); } } -void Installer::freshClone() { - qDebug() << "Doing fresh clone"; - proc.start("git", {"clone", "--progress", GIT_URL.c_str(), "-b", BRANCH_STR.c_str(), - "--depth=1", "--recurse-submodules", TMP_INSTALL_PATH}); +int freshClone() { + LOGD("Doing fresh clone"); + std::string cmd = util::string_format("git clone --progress %s -b %s --depth=1 --recurse-submodules %s 2>&1", + GIT_URL.c_str(), BRANCH_STR.c_str(), TMP_INSTALL_PATH); + return executeGitCommand(cmd); } -void Installer::cachedFetch(const QString &cache) { - qDebug() << "Fetching with cache: " << cache; +int cachedFetch(const std::string &cache) { + LOGD("Fetching with cache: %s", cache.c_str()); - run(QString("cp -rp %1 %2").arg(cache, TMP_INSTALL_PATH).toStdString().c_str()); - int err = chdir(TMP_INSTALL_PATH); - assert(err == 0); - run(("git remote set-branches --add origin " + BRANCH_STR).c_str()); + run(util::string_format("cp -rp %s %s", cache.c_str(), TMP_INSTALL_PATH).c_str()); + run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, BRANCH_STR.c_str()).c_str()); - updateProgress(10); + renderProgress(10); - proc.setWorkingDirectory(TMP_INSTALL_PATH); - proc.start("git", {"fetch", "--progress", "origin", BRANCH_STR.c_str()}); + return executeGitCommand(util::string_format("cd %s && git fetch --progress origin %s 2>&1", TMP_INSTALL_PATH, BRANCH_STR.c_str())); } -void Installer::readProgress() { - const QVector> stages = { +int executeGitCommand(const std::string &cmd) { + static const std::array stages = { // prefix, weight in percentage - {"Receiving objects: ", 91}, - {"Resolving deltas: ", 2}, - {"Updating files: ", 7}, + std::pair{"Receiving objects: ", 91}, + std::pair{"Resolving deltas: ", 2}, + std::pair{"Updating files: ", 7}, }; - auto line = QString(proc.readAllStandardError()); + FILE *pipe = popen(cmd.c_str(), "r"); + if (!pipe) return -1; - int base = 0; - for (const QPair kv : stages) { - if (line.startsWith(kv.first)) { - auto perc = line.split(kv.first)[1].split("%")[0]; - int p = base + int(perc.toFloat() / 100. * kv.second); - updateProgress(p); - break; + char buffer[512]; + while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { + std::string line(buffer); + int base = 0; + for (const auto &[text, weight] : stages) { + if (line.find(text) != std::string::npos) { + size_t percentPos = line.find("%"); + if (percentPos != std::string::npos && percentPos >= 3) { + int percent = std::stoi(line.substr(percentPos - 3, 3)); + int progress = base + int(percent / 100. * weight); + renderProgress(progress); + } + break; + } + base += weight; } - base += kv.second; } + return pclose(pipe); } -void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { - qDebug() << "git finished with " << exitCode; +void cloneFinished(int exitCode) { + LOGD("git finished with %d", exitCode); assert(exitCode == 0); - updateProgress(100); + renderProgress(100); // ensure correct branch is checked out int err = chdir(TMP_INSTALL_PATH); @@ -203,13 +171,17 @@ void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { run("mv /data/continue.sh.new " CONTINUE_PATH); // wait for the installed software's UI to take over - QTimer::singleShot(60 * 1000, &QCoreApplication::quit); + util::sleep_for(60 * 1000); } int main(int argc, char *argv[]) { - initApp(argc, argv); - QApplication a(argc, argv); - Installer installer; - setMainWindow(&installer); - return a.exec(); + InitWindow(2160, 1080, "Installer"); + font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, 120, NULL, 0); + SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); + renderProgress(0); + int result = doInstall(); + cloneFinished(result); + CloseWindow(); + UnloadFont(font); + return 0; } diff --git a/selfdrive/ui/installer/installer.h b/selfdrive/ui/installer/installer.h deleted file mode 100644 index de3af0ff39..0000000000 --- a/selfdrive/ui/installer/installer.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -class Installer : public QWidget { - Q_OBJECT - -public: - explicit Installer(QWidget *parent = 0); - -private slots: - void updateProgress(int percent); - - void readProgress(); - void cloneFinished(int exitCode, QProcess::ExitStatus exitStatus); - -private: - QLabel *val; - QProgressBar *bar; - QProcess proc; - - void doInstall(); - void freshClone(); - void cachedFetch(const QString &cache); -}; diff --git a/selfdrive/ui/installer/inter-ascii.ttf b/selfdrive/ui/installer/inter-ascii.ttf new file mode 100644 index 0000000000..5d480c515a --- /dev/null +++ b/selfdrive/ui/installer/inter-ascii.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ef26a4099ef867f3493389379d882381a2491cdbfa41a086be8899a2154dcb3 +size 26160 From 6d0cac53051c80c019bc1f03bb027a21ed945037 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 20 May 2025 17:32:27 -0700 Subject: [PATCH 118/142] MultiButtonControl (#35307) * copy * remove params from it * split out --- selfdrive/ui/qt/widgets/controls.h | 37 ++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index aebf934f2a..ca93110e5b 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -183,10 +183,10 @@ private: bool store_confirm = false; }; -class ButtonParamControl : public AbstractControl { +class MultiButtonControl : public AbstractControl { Q_OBJECT public: - ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + MultiButtonControl(const QString &title, const QString &desc, const QString &icon, const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) { const QString style = R"( QPushButton { @@ -208,24 +208,19 @@ public: color: #33E4E4E4; } )"; - key = param.toStdString(); - int value = atoi(params.get(key).c_str()); button_group = new QButtonGroup(this); button_group->setExclusive(true); for (int i = 0; i < button_texts.size(); i++) { QPushButton *button = new QPushButton(button_texts[i], this); button->setCheckable(true); - button->setChecked(i == value); button->setStyleSheet(style); button->setMinimumWidth(minimum_button_width); hlayout->addWidget(button); button_group->addButton(button, i); } - QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) { - params.put(key, std::to_string(id)); - }); + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), this, &MultiButtonControl::buttonClicked); } void setEnabled(bool enable) { @@ -238,6 +233,31 @@ public: button_group->button(id)->setChecked(true); } +signals: + void buttonClicked(int id); + +protected: + QButtonGroup *button_group; +}; + +class ButtonParamControl : public MultiButtonControl { + Q_OBJECT +public: + ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, const int minimum_button_width = 225) : MultiButtonControl(title, desc, icon, + button_texts, minimum_button_width) { + key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + + if (value > 0 && value < button_group->buttons().size()) { + button_group->button(value)->setChecked(true); + } + + QObject::connect(this, QOverload::of(&MultiButtonControl::buttonClicked), [=](int id) { + params.put(key, std::to_string(id)); + }); + } + void refresh() { int value = atoi(params.get(key).c_str()); button_group->button(value)->setChecked(true); @@ -250,7 +270,6 @@ public: private: std::string key; Params params; - QButtonGroup *button_group; }; class ListWidget : public QWidget { From 472feefcfdf20a8f4823e28baf596868fd70391b Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 21 May 2025 01:52:14 +0100 Subject: [PATCH 119/142] Revert "ui: rewrite installer using raylib, remove qt (#33756)" This reverts commit 9cd939d35497bc6d96607d4f5de3441501facd6f. --- selfdrive/ui/SConscript | 26 ++-- selfdrive/ui/installer/installer.cc | 182 ++++++++++++++----------- selfdrive/ui/installer/installer.h | 28 ++++ selfdrive/ui/installer/inter-ascii.ttf | 3 - 4 files changed, 142 insertions(+), 97 deletions(-) create mode 100644 selfdrive/ui/installer/installer.h delete mode 100644 selfdrive/ui/installer/inter-ascii.ttf diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 7199399d41..e63359da05 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,5 +1,6 @@ +import os import json -Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') +Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'transformations') base_libs = [common, messaging, visionipc, transformations, 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] @@ -75,15 +76,8 @@ if GetOption('extras'): if arch != "Darwin": # build installers - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "wayland-client", "wayland-egl", "EGL"] - else: - raylib_libs += ["GL"] + senv = qt_env.Clone() + senv['LINKFLAGS'].append('-Wl,-strip-debug') release = "release3" installers = [ @@ -93,19 +87,17 @@ if GetOption('extras'): ("openpilot_internal", "nightly-dev"), ] - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", - "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") + cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") for name, branch in installers: d = {'BRANCH': f"'\"{branch}\"'"} if "internal" in name: d['INTERNAL'] = "1" - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter], LIBS=raylib_libs) + obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs) # keep installers small - assert f[0].get_size() < 1300*1e3, f[0].get_size() + assert f[0].get_size() < 370*1e3 # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 7326e089ab..84486e61be 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -1,15 +1,19 @@ -#include -#include +#include + +#include #include #include +#include + +#include +#include +#include +#include -#include "common/swaglog.h" #include "common/util.h" -#include "third_party/raylib/include/raylib.h" - -int freshClone(); -int cachedFetch(const std::string &cache); -int executeGitCommand(const std::string &cmd); +#include "selfdrive/ui/installer/installer.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" std::string get_str(std::string const s) { std::string::size_type pos = s.find('?'); @@ -24,108 +28,136 @@ const std::string BRANCH_STR = get_str(BRANCH "? #define GIT_SSH_URL "git@github.com:commaai/openpilot.git" #define CONTINUE_PATH "/data/continue.sh" -const std::string CACHE_PATH = "/data/openpilot.cache"; +const QString CACHE_PATH = "/data/openpilot.cache"; #define INSTALL_PATH "/data/openpilot" #define TMP_INSTALL_PATH "/data/tmppilot" extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); -extern const uint8_t inter_ttf[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_start"); -extern const uint8_t inter_ttf_end[] asm("_binary_selfdrive_ui_installer_inter_ascii_ttf_end"); - -Font font; void run(const char* cmd) { int err = std::system(cmd); assert(err == 0); } -void renderProgress(int progress) { - BeginDrawing(); - ClearBackground(BLACK); - DrawTextEx(font, "Installing...", (Vector2){150, 290}, 110, 0, WHITE); - Rectangle bar = {150, 570, (float)GetScreenWidth() - 300, 72}; - DrawRectangleRec(bar, (Color){41, 41, 41, 255}); - progress = std::clamp(progress, 0, 100); - bar.width *= progress / 100.0f; - DrawRectangleRec(bar, (Color){70, 91, 234, 255}); - DrawTextEx(font, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); - EndDrawing(); +Installer::Installer(QWidget *parent) : QWidget(parent) { + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(150, 290, 150, 150); + layout->setSpacing(0); + + QLabel *title = new QLabel(tr("Installing...")); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + layout->addWidget(title, 0, Qt::AlignTop); + + layout->addSpacing(170); + + bar = new QProgressBar(); + bar->setRange(0, 100); + bar->setTextVisible(false); + bar->setFixedHeight(72); + layout->addWidget(bar, 0, Qt::AlignTop); + + layout->addSpacing(30); + + val = new QLabel("0%"); + val->setStyleSheet("font-size: 70px; font-weight: 300;"); + layout->addWidget(val, 0, Qt::AlignTop); + + layout->addStretch(); + + QObject::connect(&proc, QOverload::of(&QProcess::finished), this, &Installer::cloneFinished); + QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Installer::readProgress); + + QTimer::singleShot(100, this, &Installer::doInstall); + + setStyleSheet(R"( + * { + font-family: Inter; + color: white; + background-color: black; + } + QProgressBar { + border: none; + background-color: #292929; + } + QProgressBar::chunk { + background-color: #364DEF; + } + )"); } -int doInstall() { +void Installer::updateProgress(int percent) { + bar->setValue(percent); + val->setText(QString("%1%").arg(percent)); + update(); +} + +void Installer::doInstall() { // wait for valid time while (!util::system_time_valid()) { - util::sleep_for(500); - LOGD("Waiting for valid time"); + usleep(500 * 1000); + qDebug() << "Waiting for valid time"; } // cleanup previous install attempts run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH); // do the install - if (util::file_exists(CACHE_PATH)) { - return cachedFetch(CACHE_PATH); + if (QDir(CACHE_PATH).exists()) { + cachedFetch(CACHE_PATH); } else { - return freshClone(); + freshClone(); } } -int freshClone() { - LOGD("Doing fresh clone"); - std::string cmd = util::string_format("git clone --progress %s -b %s --depth=1 --recurse-submodules %s 2>&1", - GIT_URL.c_str(), BRANCH_STR.c_str(), TMP_INSTALL_PATH); - return executeGitCommand(cmd); +void Installer::freshClone() { + qDebug() << "Doing fresh clone"; + proc.start("git", {"clone", "--progress", GIT_URL.c_str(), "-b", BRANCH_STR.c_str(), + "--depth=1", "--recurse-submodules", TMP_INSTALL_PATH}); } -int cachedFetch(const std::string &cache) { - LOGD("Fetching with cache: %s", cache.c_str()); +void Installer::cachedFetch(const QString &cache) { + qDebug() << "Fetching with cache: " << cache; - run(util::string_format("cp -rp %s %s", cache.c_str(), TMP_INSTALL_PATH).c_str()); - run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, BRANCH_STR.c_str()).c_str()); + run(QString("cp -rp %1 %2").arg(cache, TMP_INSTALL_PATH).toStdString().c_str()); + int err = chdir(TMP_INSTALL_PATH); + assert(err == 0); + run(("git remote set-branches --add origin " + BRANCH_STR).c_str()); - renderProgress(10); + updateProgress(10); - return executeGitCommand(util::string_format("cd %s && git fetch --progress origin %s 2>&1", TMP_INSTALL_PATH, BRANCH_STR.c_str())); + proc.setWorkingDirectory(TMP_INSTALL_PATH); + proc.start("git", {"fetch", "--progress", "origin", BRANCH_STR.c_str()}); } -int executeGitCommand(const std::string &cmd) { - static const std::array stages = { +void Installer::readProgress() { + const QVector> stages = { // prefix, weight in percentage - std::pair{"Receiving objects: ", 91}, - std::pair{"Resolving deltas: ", 2}, - std::pair{"Updating files: ", 7}, + {"Receiving objects: ", 91}, + {"Resolving deltas: ", 2}, + {"Updating files: ", 7}, }; - FILE *pipe = popen(cmd.c_str(), "r"); - if (!pipe) return -1; + auto line = QString(proc.readAllStandardError()); - char buffer[512]; - while (fgets(buffer, sizeof(buffer), pipe) != nullptr) { - std::string line(buffer); - int base = 0; - for (const auto &[text, weight] : stages) { - if (line.find(text) != std::string::npos) { - size_t percentPos = line.find("%"); - if (percentPos != std::string::npos && percentPos >= 3) { - int percent = std::stoi(line.substr(percentPos - 3, 3)); - int progress = base + int(percent / 100. * weight); - renderProgress(progress); - } - break; - } - base += weight; + int base = 0; + for (const QPair kv : stages) { + if (line.startsWith(kv.first)) { + auto perc = line.split(kv.first)[1].split("%")[0]; + int p = base + int(perc.toFloat() / 100. * kv.second); + updateProgress(p); + break; } + base += kv.second; } - return pclose(pipe); } -void cloneFinished(int exitCode) { - LOGD("git finished with %d", exitCode); +void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "git finished with " << exitCode; assert(exitCode == 0); - renderProgress(100); + updateProgress(100); // ensure correct branch is checked out int err = chdir(TMP_INSTALL_PATH); @@ -171,17 +203,13 @@ void cloneFinished(int exitCode) { run("mv /data/continue.sh.new " CONTINUE_PATH); // wait for the installed software's UI to take over - util::sleep_for(60 * 1000); + QTimer::singleShot(60 * 1000, &QCoreApplication::quit); } int main(int argc, char *argv[]) { - InitWindow(2160, 1080, "Installer"); - font = LoadFontFromMemory(".ttf", inter_ttf, inter_ttf_end - inter_ttf, 120, NULL, 0); - SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); - renderProgress(0); - int result = doInstall(); - cloneFinished(result); - CloseWindow(); - UnloadFont(font); - return 0; + initApp(argc, argv); + QApplication a(argc, argv); + Installer installer; + setMainWindow(&installer); + return a.exec(); } diff --git a/selfdrive/ui/installer/installer.h b/selfdrive/ui/installer/installer.h new file mode 100644 index 0000000000..de3af0ff39 --- /dev/null +++ b/selfdrive/ui/installer/installer.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +class Installer : public QWidget { + Q_OBJECT + +public: + explicit Installer(QWidget *parent = 0); + +private slots: + void updateProgress(int percent); + + void readProgress(); + void cloneFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QLabel *val; + QProgressBar *bar; + QProcess proc; + + void doInstall(); + void freshClone(); + void cachedFetch(const QString &cache); +}; diff --git a/selfdrive/ui/installer/inter-ascii.ttf b/selfdrive/ui/installer/inter-ascii.ttf deleted file mode 100644 index 5d480c515a..0000000000 --- a/selfdrive/ui/installer/inter-ascii.ttf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1ef26a4099ef867f3493389379d882381a2491cdbfa41a086be8899a2154dcb3 -size 26160 From 350a235303029020a365a4819888a3ba25635223 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 20 May 2025 19:41:58 -0700 Subject: [PATCH 120/142] modeld: more USB GPU fixes (#35306) * fixups * builds --- selfdrive/modeld/SConscript | 45 +++++++++++++------ selfdrive/modeld/modeld.py | 4 +- .../modeld/models/big_driving_policy.onnx | 1 + .../modeld/models/big_driving_vision.onnx | 1 + tinygrad_repo | 2 +- 5 files changed, 37 insertions(+), 16 deletions(-) create mode 120000 selfdrive/modeld/models/big_driving_policy.onnx create mode 120000 selfdrive/modeld/models/big_driving_vision.onnx diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index a6be5c15da..10f3876190 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -38,18 +38,35 @@ for model_name in ['driving_vision', 'driving_policy']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) -# Compile tinygrad model -pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' -for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - if "USBGPU" in os.environ and not model_name.startswith("dmon"): - device_string = "AMD=1 AMD_LLVM=1 NOLOCALS=0 IMAGE=0" - elif arch == 'larch64': - device_string = 'QCOM=1' - elif arch == 'Darwin': - device_string = 'CPU=1 IMAGE=0 JIT=2' - else: - device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2' - +def tg_compile(flags, model_name): + pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - cmd = f'{pythonpath_string} {device_string} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' - lenv.Command(fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, cmd) + return lenv.Command( + fn + "_tinygrad.pkl", + [fn + ".onnx"] + tinygrad_files, + f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + ) + +# Compile small models +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: + flags = { + 'larch64': 'QCOM=1', + 'Darwin': 'CPU=1 IMAGE=0 JIT=2', + }.get(arch, 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0 JIT=2') + tg_compile(flags, model_name) + +# Compile BIG model if USB GPU is available +import subprocess +from tinygrad import Device + +# because tg doesn't support multi-process +devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True) +if b"AMD" in devs: + del Device + print("USB GPU detected... building") + flags = "AMD=1 AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0" + bp = tg_compile(flags, "big_driving_policy") + bv = tg_compile(flags, "big_driving_vision") + lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially +else: + print("USB GPU not detected... skipping") diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index cdb16a6f63..1e3d1782e6 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -4,6 +4,7 @@ from openpilot.system.hardware import TICI USBGPU = "USBGPU" in os.environ if USBGPU: os.environ['AMD'] = '1' + os.environ['AMD_IFACE'] = 'USB' elif TICI: from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address os.environ['QCOM'] = '1' @@ -196,11 +197,12 @@ def main(demo=False): # also need to move the aux USB interrupts for good timings config_realtime_process(7, 54) + st = time.monotonic() cloudlog.warning("setting up CL context") cl_context = CLContext() cloudlog.warning("CL context ready; loading model") model = ModelState(cl_context) - cloudlog.warning("models loaded, modeld starting") + cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients while True: diff --git a/selfdrive/modeld/models/big_driving_policy.onnx b/selfdrive/modeld/models/big_driving_policy.onnx new file mode 120000 index 0000000000..e1b653a14a --- /dev/null +++ b/selfdrive/modeld/models/big_driving_policy.onnx @@ -0,0 +1 @@ +driving_policy.onnx \ No newline at end of file diff --git a/selfdrive/modeld/models/big_driving_vision.onnx b/selfdrive/modeld/models/big_driving_vision.onnx new file mode 120000 index 0000000000..28ee71dd74 --- /dev/null +++ b/selfdrive/modeld/models/big_driving_vision.onnx @@ -0,0 +1 @@ +driving_vision.onnx \ No newline at end of file diff --git a/tinygrad_repo b/tinygrad_repo index 2895198c36..519dec6677 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 2895198c368e226e2176aae37d5e545691ce85cb +Subproject commit 519dec6677f98718ee4f2d07be1936eb91dde73b From 7ea8277e399e57937379323650d0d7888f95d80d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 21 May 2025 19:28:04 +0800 Subject: [PATCH 121/142] system/ui: improve button press behavior (#35309) improve button press behavior --- system/ui/lib/button.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py index 4870e333df..9ca82c9732 100644 --- a/system/ui/lib/button.py +++ b/system/ui/lib/button.py @@ -68,19 +68,22 @@ def gui_button( # Set background color based on button type bg_color = BUTTON_BACKGROUND_COLORS[button_style] mouse_over = is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect) + is_pressed = button_id in _pressed_buttons if mouse_over: if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - # Record that this button was pressed + # Only this button enters pressed state _pressed_buttons.add(button_id) + is_pressed = True + + # Use pressed color when mouse is down over this button + if is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style] - elif rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): - bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style] - elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): - # Check if this button was previously pressed - if button_id in _pressed_buttons: - result = 1 - _pressed_buttons.remove(button_id) + + # Handle button click + if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and is_pressed: + result = 1 + _pressed_buttons.remove(button_id) # Clean up pressed state if mouse is released anywhere if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and button_id in _pressed_buttons: From 4c9655b5e4e87e0abc35bd442e3075af61fc1c19 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 22 May 2025 00:44:49 +0800 Subject: [PATCH 122/142] system/ui: add text scrolling support to InputBox for long text (#35310) * add text scrolling support to InputBox for long text * add 2 pixels buffer to the scissor region --- system/ui/lib/inputbox.py | 66 +++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index 78ee03bdbd..b164fa4f2a 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -14,6 +14,8 @@ class InputBox: self._key_press_time = 0 self._repeat_delay = 30 self._repeat_rate = 4 + self._text_offset = 0 + self._visible_width = 0 @property def text(self): @@ -23,6 +25,7 @@ class InputBox: def text(self, value): self._input_text = value[: self._max_text_size] self._cursor_position = len(self._input_text) + self._update_text_offset() def set_password_mode(self, password_mode): self._password_mode = password_mode @@ -30,6 +33,7 @@ class InputBox: def clear(self): self._input_text = '' self._cursor_position = 0 + self._text_offset = 0 def set_cursor_position(self, position): """Set the cursor position and reset the blink counter.""" @@ -37,6 +41,29 @@ class InputBox: self._cursor_position = position self._blink_counter = 0 self._show_cursor = True + self._update_text_offset() + + def _update_text_offset(self): + """Ensure the cursor is visible by adjusting text offset.""" + if self._visible_width == 0: + return + + font = gui_app.font() + display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + padding = 10 + + if self._cursor_position > 0: + cursor_x = rl.measure_text_ex(font, display_text[: self._cursor_position], self._font_size, 0).x + else: + cursor_x = 0 + + visible_width = self._visible_width - (padding * 2) + + # Adjust offset if cursor would be outside visible area + if cursor_x < self._text_offset: + self._text_offset = max(0, cursor_x - padding) + elif cursor_x > self._text_offset + visible_width: + self._text_offset = cursor_x - visible_width + padding def add_char_at_cursor(self, char): """Add a character at the current cursor position.""" @@ -63,6 +90,10 @@ class InputBox: return False def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80): + # Store dimensions for text offset calculations + self._visible_width = rect.width + self._font_size = font_size + # Handle mouse input self._handle_mouse_input(rect, font_size) @@ -82,10 +113,14 @@ class InputBox: font = gui_app.font() display_text = "*" * len(self._input_text) if self._password_mode else self._input_text padding = 10 + + # Clip text within input box bounds + buffer = 2 + rl.begin_scissor_mode(int(rect.x + padding - buffer), int(rect.y), int(rect.width - padding * 2 + buffer * 2), int(rect.height)) rl.draw_text_ex( font, display_text, - rl.Vector2(int(rect.x + padding), int(rect.y + rect.height / 2 - font_size / 2)), + rl.Vector2(int(rect.x + padding - self._text_offset), int(rect.y + rect.height / 2 - font_size / 2)), font_size, 0, text_color, @@ -97,9 +132,14 @@ class InputBox: if len(display_text) > 0 and self._cursor_position > 0: cursor_x += rl.measure_text_ex(font, display_text[: self._cursor_position], font_size, 0).x + # Apply text offset to cursor position + cursor_x -= self._text_offset + cursor_height = font_size + 4 cursor_y = rect.y + rect.height / 2 - cursor_height / 2 - rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.LIGHTGRAY) + rl.draw_line(int(cursor_x), int(cursor_y), int(cursor_x), int(cursor_y + cursor_height), rl.WHITE) + + rl.end_scissor_mode() def _handle_mouse_input(self, rect, font_size): """Handle mouse clicks to position cursor.""" @@ -107,14 +147,22 @@ class InputBox: if rl.is_mouse_button_pressed(rl.MOUSE_LEFT_BUTTON) and rl.check_collision_point_rec(mouse_pos, rect): # Calculate cursor position from click if len(self._input_text) > 0: - text_width = rl.measure_text_ex(gui_app.font(), self._input_text, font_size, 0).x - text_pos_x = rect.x + 10 + font = gui_app.font() + display_text = "*" * len(self._input_text) if self._password_mode else self._input_text - if mouse_pos.x - text_pos_x > text_width: - self.set_cursor_position(len(self._input_text)) - else: - click_ratio = (mouse_pos.x - text_pos_x) / text_width - self.set_cursor_position(int(len(self._input_text) * click_ratio)) + # Find the closest character position to the click + relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset + best_pos = 0 + min_distance = float('inf') + + for i in range(len(self._input_text) + 1): + char_width = rl.measure_text_ex(font, display_text[:i], font_size, 0).x + distance = abs(relative_x - char_width) + if distance < min_distance: + min_distance = distance + best_pos = i + + self.set_cursor_position(best_pos) else: self.set_cursor_position(0) From 68fa8c56ab73169612312914147270baace2664c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 22 May 2025 02:17:03 +0800 Subject: [PATCH 123/142] =?UTF-8?q?system/ui:=20use=20=E2=80=A2=20for=20pa?= =?UTF-8?q?ssword=20masking=20in=20InputBox=20(#35313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit use • for password masking --- system/ui/lib/inputbox.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index b164fa4f2a..87e0e92d51 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -2,6 +2,9 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app +PASSWORD_MASK_CHAR = "•" + + class InputBox: def __init__(self, max_text_size=255, password_mode=False): self._max_text_size = max_text_size @@ -49,7 +52,7 @@ class InputBox: return font = gui_app.font() - display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text padding = 10 if self._cursor_position > 0: @@ -111,7 +114,7 @@ class InputBox: # Display text font = gui_app.font() - display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text padding = 10 # Clip text within input box bounds @@ -148,7 +151,7 @@ class InputBox: # Calculate cursor position from click if len(self._input_text) > 0: font = gui_app.font() - display_text = "*" * len(self._input_text) if self._password_mode else self._input_text + display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text # Find the closest character position to the click relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset From 19ad50a185bb80bbe2de4f6154cc0b93bafee506 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 21 May 2025 11:33:47 -0700 Subject: [PATCH 124/142] feat(route.py): add metadata, events retrieval (#35261) * feat(route.py): add metadata, events retrieval * whitespace * raise for status --- tools/lib/route.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tools/lib/route.py b/tools/lib/route.py index ddb8b0486e..0a4700f083 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -1,12 +1,13 @@ import os import re +import requests from functools import cache from urllib.parse import urlparse from collections import defaultdict from itertools import chain from openpilot.tools.lib.auth_config import get_token -from openpilot.tools.lib.api import CommaApi +from openpilot.tools.lib.api import APIError, CommaApi from openpilot.tools.lib.helpers import RE QLOG_FILENAMES = ['qlog', 'qlog.bz2', 'qlog.zst'] @@ -19,6 +20,7 @@ ECAMERA_FILENAMES = ['ecamera.hevc'] class Route: def __init__(self, name, data_dir=None): + self._metadata = None self._name = RouteName(name) self.files = None if data_dir is not None: @@ -27,6 +29,13 @@ class Route: self._segments = self._get_segments_remote() self.max_seg_number = self._segments[-1].name.segment_num + @property + def metadata(self): + if not self._metadata: + api = CommaApi(get_token()) + self._metadata = api.get('v1/route/' + self.name.canonical_name) + return self._metadata + @property def name(self): return self._name @@ -78,6 +87,7 @@ class Route: url if fn in DCAMERA_FILENAMES else segments[segment_name].dcamera_path, url if fn in ECAMERA_FILENAMES else segments[segment_name].ecamera_path, url if fn in QCAMERA_FILENAMES else segments[segment_name].qcamera_path, + self.metadata['url'], ) else: segments[segment_name] = Segment( @@ -88,6 +98,7 @@ class Route: url if fn in DCAMERA_FILENAMES else None, url if fn in ECAMERA_FILENAMES else None, url if fn in QCAMERA_FILENAMES else None, + self.metadata['url'], ) return sorted(segments.values(), key=lambda seg: seg.name.segment_num) @@ -153,7 +164,7 @@ class Route: except StopIteration: qcamera_path = None - segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) + segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, self.metadata['url'])) if len(segments) == 0: raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}') @@ -161,8 +172,10 @@ class Route: class Segment: - def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path): + def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, url): + self._events = None self._name = SegmentName(name) + self.url = f'{url}/{self._name.segment_num}' self.log_path = log_path self.qlog_path = qlog_path self.camera_path = camera_path @@ -174,6 +187,17 @@ class Segment: def name(self): return self._name + @property + def events(self): + if not self._events: + try: + resp = requests.get(f'{self.url}/events.json') + resp.raise_for_status() + self._events = resp.json() + except Exception as e: + raise APIError(f'error getting events for segment {self._name}') from e + return self._events + class RouteName: def __init__(self, name_str: str): From b4d6b52edd815364b9d3503aff6dd3ce1005fe45 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Wed, 21 May 2025 11:48:49 -0700 Subject: [PATCH 125/142] Vegan Filet o Fish model (#35240) e30305e9-5fb8-48bd-a278-f714e880aef1/700 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 3601bbb5da..5da6d85300 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98f0121ccb6f850077b04cc91bd33d370fc6cbdc2bd35f1ab55628a15a813f36 -size 15966721 +oid sha256:abbb1f5a74a6d047ed4b7f376b19451a9443986660f136afd0f8d76fc254a579 +size 15976894 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index aee6d8f1bf..6c8cf592b0 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:897f80d0388250f99bba69b6a8434560cc0fd83157cbeb0bc134c67fe4e64624 +oid sha256:6dfffac033d7ae8e68aa5763bd9b5dd99ddc748f6a95523c84fc2523eefdec55 size 34882971 From 4423b47b6c11ca8148b18feaed8e24ca194b339f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 22 May 2025 03:44:15 +0800 Subject: [PATCH 126/142] system/ui: add password mask delay to InputBox (#35316) add password mask delay to InputBox --- system/ui/lib/inputbox.py | 29 ++++++++++++++++++++++++++--- system/ui/widgets/keyboard.py | 2 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/system/ui/lib/inputbox.py b/system/ui/lib/inputbox.py index 87e0e92d51..367e0dc7e4 100644 --- a/system/ui/lib/inputbox.py +++ b/system/ui/lib/inputbox.py @@ -1,8 +1,10 @@ import pyray as rl +import time from openpilot.system.ui.lib.application import gui_app PASSWORD_MASK_CHAR = "•" +PASSWORD_MASK_DELAY = 1.5 # Seconds to show character before masking class InputBox: @@ -19,6 +21,8 @@ class InputBox: self._repeat_rate = 4 self._text_offset = 0 self._visible_width = 0 + self._last_char_time = 0 # Track when last character was added + self._masked_length = 0 # How many characters are currently masked @property def text(self): @@ -52,7 +56,7 @@ class InputBox: return font = gui_app.font() - display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text + display_text = self._get_display_text() padding = 10 if self._cursor_position > 0: @@ -73,6 +77,10 @@ class InputBox: if len(self._input_text) < self._max_text_size: self._input_text = self._input_text[: self._cursor_position] + char + self._input_text[self._cursor_position :] self.set_cursor_position(self._cursor_position + 1) + + if self._password_mode: + self._last_char_time = time.time() + return True return False @@ -114,7 +122,7 @@ class InputBox: # Display text font = gui_app.font() - display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text + display_text = self._get_display_text() padding = 10 # Clip text within input box bounds @@ -144,6 +152,21 @@ class InputBox: rl.end_scissor_mode() + def _get_display_text(self): + """Get text to display, applying password masking with delay if needed.""" + if not self._password_mode: + return self._input_text + + # Show character at last edited position if within delay window + masked_text = PASSWORD_MASK_CHAR * len(self._input_text) + recent_edit = time.time() - self._last_char_time < PASSWORD_MASK_DELAY + if recent_edit and self._input_text: + last_pos = max(0, self._cursor_position - 1) + if last_pos < len(self._input_text): + return masked_text[:last_pos] + self._input_text[last_pos] + masked_text[last_pos + 1 :] + + return masked_text + def _handle_mouse_input(self, rect, font_size): """Handle mouse clicks to position cursor.""" mouse_pos = rl.get_mouse_position() @@ -151,7 +174,7 @@ class InputBox: # Calculate cursor position from click if len(self._input_text) > 0: font = gui_app.font() - display_text = PASSWORD_MASK_CHAR * len(self._input_text) if self._password_mode else self._input_text + display_text = self._get_display_text() # Find the closest character position to the click relative_x = mouse_pos.x - (rect.x + 10) + self._text_offset diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 7836b28184..0e22f1b527 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -221,7 +221,7 @@ class Keyboard: if __name__ == "__main__": gui_app.init_window("Keyboard") - keyboard = Keyboard(min_text_size=8) + keyboard = Keyboard(min_text_size=8, show_password_toggle=True) for _ in gui_app.render(): result = keyboard.render("Keyboard", "Type here") if result == 1: From 786b46c0b4621638aa72e67321d338a8157c53dc Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 21 May 2025 12:45:05 -0700 Subject: [PATCH 127/142] feat: `op esim` and esim.py 2 (#35314) * init: lpa interface * handle multiple messages * handle timeouts * delete old LPA, add enable/disable/validation * check if valid * keep old file the same for easier diff * keep * nickname, bug fixes * space * simple * need to test this on slow conn * initial HITL test for eSIM provisioning * cleanup * lint * test flakes if lpac called concurrently * no * cleanup * org * comment * vibe coded uts * Revert "vibe coded uts" This reverts commit 8b4d8f8ade50dbeaf3fa44f1df1aa2d809deca18. * much simpler test * no value * remove no value add comments * only one test flow now * simpler * reorganize * replace impl * brevity * moar * why didnt u rename * moar * check lpac installed * Profile dataclass * shorten * print out profiles * better * plurals * argparse * download/nickname * move to end to show change * just end early if already enabled * --reboot * reconfigure conn * mutations require reboot today * not needed * lint * guard delete * better * print help * spaceg * rename * support at device * choose backend * desc * more * brackets * op esim * Revert "brackets" This reverts commit 124dbc0cbcc07d93f556ca80a6dc47aa118fda5c. * Update Jenkinsfile --- Jenkinsfile | 2 + system/hardware/tici/esim.py | 233 +++++++++++++++--------- system/hardware/tici/tests/test_esim.py | 51 ++++++ tools/op.sh | 7 + 4 files changed, 204 insertions(+), 89 deletions(-) create mode 100644 system/hardware/tici/tests/test_esim.py diff --git a/Jenkinsfile b/Jenkinsfile index b1a0746ea3..a14bf59299 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -268,6 +268,8 @@ node { step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"), step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"), + // TODO: enable once new AGNOS is available + // step("test esim", "pytest system/hardware/tici/tests/test_esim.py"), step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]), ]) }, diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index df76c1a5fd..0a07e77e16 100755 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -1,115 +1,170 @@ #!/usr/bin/env python3 + +import argparse +import json import os -import math -import time -import binascii -import requests -import serial +import shutil import subprocess +from dataclasses import dataclass +from typing import Literal +@dataclass +class Profile: + iccid: str + nickname: str + enabled: bool + provider: str -def post(url, payload): - print() - print("POST to", url) - r = requests.post( - url, - data=payload, - verify=False, - headers={ - "Content-Type": "application/json", - "X-Admin-Protocol": "gsma/rsp/v2.2.0", - "charset": "utf-8", - "User-Agent": "gsma-rsp-lpad", - }, - ) - print("resp", r) - print("resp text", repr(r.text)) - print() - r.raise_for_status() +class LPAError(RuntimeError): + pass - ret = f"HTTP/1.1 {r.status_code}" - ret += ''.join(f"{k}: {v}" for k, v in r.headers.items() if k != 'Connection') - return ret.encode() + r.content +class LPAProfileNotFoundError(LPAError): + pass class LPA: - def __init__(self): - self.dev = serial.Serial('/dev/ttyUSB2', baudrate=57600, timeout=1, bytesize=8) - self.dev.reset_input_buffer() - self.dev.reset_output_buffer() - assert "OK" in self.at("AT") + def __init__(self, interface: Literal['qmi', 'at'] = 'qmi'): + self.env = os.environ.copy() + self.env['LPAC_APDU'] = interface + self.env['QMI_DEVICE'] = '/dev/cdc-wdm0' + self.env['AT_DEVICE'] = '/dev/ttyUSB2' - def at(self, cmd): - print(f"==> {cmd}") - self.dev.write(cmd.encode() + b'\r\n') + self.timeout_sec = 45 - r = b"" - cnt = 0 - while b"OK" not in r and b"ERROR" not in r and cnt < 20: - r += self.dev.read(8192).strip() - cnt += 1 - r = r.decode() - print(f"<== {repr(r)}") - return r + if shutil.which('lpac') is None: + raise LPAError('lpac not found, must be installed!') - def download_ota(self, qr): - return self.at(f'AT+QESIM="ota","{qr}"') + def list_profiles(self) -> list[Profile]: + msgs = self._invoke('profile', 'list') + self._validate_successful(msgs) + return [Profile( + iccid=p['iccid'], + nickname=p['profileNickname'], + enabled=p['profileState'] == 'enabled', + provider=p['serviceProviderName'] + ) for p in msgs[-1]['payload']['data']] - def download(self, qr): - smdp = qr.split('$')[1] - out = self.at(f'AT+QESIM="download","{qr}"') - for _ in range(5): - line = out.split("+QESIM: ")[1].split("\r\n\r\nOK")[0] + def get_active_profile(self) -> Profile | None: + return next((p for p in self.list_profiles() if p.enabled), None) - parts = [x.strip().strip('"') for x in line.split(',', maxsplit=4)] - print(repr(parts)) - trans, ret, url, payloadlen, payload = parts - assert trans == "trans" and ret == "0" - assert len(payload) == int(payloadlen) + def enable_profile(self, iccid: str) -> None: + self._validate_profile_exists(iccid) + latest = self.get_active_profile() + if latest: + if latest.iccid == iccid: + return + self.disable_profile(latest.iccid) + self._validate_successful(self._invoke('profile', 'enable', iccid)) + self.process_notifications() - r = post(f"https://{smdp}/{url}", payload) - to_send = binascii.hexlify(r).decode() + def disable_profile(self, iccid: str) -> None: + self._validate_profile_exists(iccid) + latest = self.get_active_profile() + if latest is not None and latest.iccid != iccid: + return + self._validate_successful(self._invoke('profile', 'disable', iccid)) + self.process_notifications() - chunk_len = 1400 - for i in range(math.ceil(len(to_send) / chunk_len)): - state = 1 if (i+1)*chunk_len < len(to_send) else 0 - data = to_send[i * chunk_len : (i+1)*chunk_len] - out = self.at(f'AT+QESIM="trans",{len(to_send)},{state},{i},{len(data)},"{data}"') - assert "OK" in out + def delete_profile(self, iccid: str) -> None: + self._validate_profile_exists(iccid) + latest = self.get_active_profile() + if latest is not None and latest.iccid == iccid: + self.disable_profile(iccid) + self._validate_successful(self._invoke('profile', 'delete', iccid)) + self.process_notifications() - if '+QESIM:"download",1' in out: - raise Exception("profile install failed") - elif '+QESIM:"download",0' in out: - print("done, successfully loaded") - break + def download_profile(self, qr: str, nickname: str | None = None) -> None: + msgs = self._invoke('profile', 'download', '-a', qr) + self._validate_successful(msgs) + new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None) + if new_profile is None: + raise LPAError('no new profile found') + if nickname: + self.nickname_profile(new_profile['payload']['data']['iccid'], nickname) + self.process_notifications() - def enable(self, iccid): - self.at(f'AT+QESIM="enable","{iccid}"') + def nickname_profile(self, iccid: str, nickname: str) -> None: + self._validate_profile_exists(iccid) + self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) - def disable(self, iccid): - self.at(f'AT+QESIM="disable","{iccid}"') + def process_notifications(self) -> None: + """ + Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier. + """ + self._validate_successful(self._invoke('notification', 'process', '-a', '-r')) - def delete(self, iccid): - self.at(f'AT+QESIM="delete","{iccid}"') + def _invoke(self, *cmd: str): + proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) + try: + out, err = proc.communicate(timeout=self.timeout_sec) + except subprocess.TimeoutExpired as e: + proc.kill() + raise LPAError(f"lpac {cmd} timed out after {self.timeout_sec} seconds") from e - def list_profiles(self): - out = self.at('AT+QESIM="list"') - return out.strip().splitlines()[1:] + messages = [] + for line in out.decode().strip().splitlines(): + if line.startswith('{'): + message = json.loads(line) + + # lpac response format validations + assert 'type' in message, 'expected type in message' + assert message['type'] == 'lpa' or message['type'] == 'progress', 'expected lpa or progress message type' + assert 'payload' in message, 'expected payload in message' + assert 'code' in message['payload'], 'expected code in message payload' + assert 'data' in message['payload'], 'expected data in message payload' + + msg_ret_code = message['payload']['code'] + if msg_ret_code != 0: + raise LPAError(f"lpac {' '.join(cmd)} failed with code {msg_ret_code}: <{message['payload']['message']}> {message['payload']['data']}") + + messages.append(message) + + if len(messages) == 0: + raise LPAError(f"lpac {cmd} returned no messages") + + return messages + + def _validate_profile_exists(self, iccid: str) -> None: + if not any(p.iccid == iccid for p in self.list_profiles()): + raise LPAProfileNotFoundError(f'profile {iccid} does not exist') + + def _validate_successful(self, msgs: list[dict]) -> None: + assert msgs[-1]['payload']['message'] == 'success', 'expected success notification' if __name__ == "__main__": - import sys + parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') + parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') + parser.add_argument('--enable', metavar='iccid', help='enable profile; will disable current profile') + parser.add_argument('--disable', metavar='iccid', help='disable profile') + parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') + parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') + parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile') + args = parser.parse_args() - if "RESTART" in os.environ: - subprocess.check_call("sudo systemctl stop ModemManager", shell=True) - subprocess.check_call("/usr/comma/lte/lte.sh stop_blocking", shell=True) - subprocess.check_call("/usr/comma/lte/lte.sh start", shell=True) - while not os.path.exists('/dev/ttyUSB2'): - time.sleep(1) - time.sleep(3) + lpa = LPA(interface=args.backend) + if args.enable: + lpa.enable_profile(args.enable) + print('enabled profile, please restart device to apply changes') + elif args.disable: + lpa.disable_profile(args.disable) + print('disabled profile, please restart device to apply changes') + elif args.delete: + confirm = input('are you sure you want to delete this profile? (y/N) ') + if confirm == 'y': + lpa.delete_profile(args.delete) + print('deleted profile, please restart device to apply changes') + else: + print('cancelled') + exit(0) + elif args.download: + lpa.download_profile(args.download[0], args.download[1]) + elif args.nickname: + lpa.nickname_profile(args.nickname[0], args.nickname[1]) + else: + parser.print_help() - lpa = LPA() - print(lpa.list_profiles()) - if len(sys.argv) > 1: - lpa.download(sys.argv[1]) - print(lpa.list_profiles()) + profiles = lpa.list_profiles() + print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') + for p in profiles: + print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py new file mode 100644 index 0000000000..d36bdaa27b --- /dev/null +++ b/system/hardware/tici/tests/test_esim.py @@ -0,0 +1,51 @@ +import pytest + +from openpilot.system.hardware import TICI +from openpilot.system.hardware.tici.esim import LPA, LPAProfileNotFoundError + +# https://euicc-manual.osmocom.org/docs/rsp/known-test-profile +# iccid is always the same for the given activation code +TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5' +TEST_ICCID = '8944476500001944011' + +TEST_NICKNAME = 'test_profile' + +def cleanup(): + lpa = LPA() + try: + lpa.delete_profile(TEST_ICCID) + except LPAProfileNotFoundError: + pass + lpa.process_notifications() + +class TestEsim: + + @classmethod + def setup_class(cls): + if not TICI: + pytest.skip() + cleanup() + + @classmethod + def teardown_class(cls): + cleanup() + + def test_provision_enable_disable(self): + lpa = LPA() + current_active = lpa.get_active_profile() + + lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME) + assert any(p.iccid == TEST_ICCID and p.nickname == TEST_NICKNAME for p in lpa.list_profiles()) + + lpa.enable_profile(TEST_ICCID) + new_active = lpa.get_active_profile() + assert new_active is not None + assert new_active.iccid == TEST_ICCID + assert new_active.nickname == TEST_NICKNAME + + lpa.disable_profile(TEST_ICCID) + new_active = lpa.get_active_profile() + assert new_active is None + + if current_active: + lpa.enable_profile(current_active.iccid) diff --git a/tools/op.sh b/tools/op.sh index a7ed964812..7c17c949e4 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -293,6 +293,11 @@ function op_check() { unset VERBOSE } +function op_esim() { + op_before_cmd + op_run_command system/hardware/tici/esim.py "$@" +} + function op_build() { CDIR=$(pwd) op_before_cmd @@ -392,6 +397,7 @@ function op_default() { echo -e "${BOLD}${UNDERLINE}Commands [System]:${NC}" echo -e " ${BOLD}auth${NC} Authenticate yourself for API use" echo -e " ${BOLD}check${NC} Check the development environment (git, os, python) to start using openpilot" + echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device" echo -e " ${BOLD}venv${NC} Activate the python virtual environment" echo -e " ${BOLD}setup${NC} Install openpilot dependencies" echo -e " ${BOLD}build${NC} Run the openpilot build system in the current working directory" @@ -448,6 +454,7 @@ function _op() { auth ) shift 1; op_auth "$@" ;; venv ) shift 1; op_venv "$@" ;; check ) shift 1; op_check "$@" ;; + esim ) shift 1; op_esim "$@" ;; setup ) shift 1; op_setup "$@" ;; build ) shift 1; op_build "$@" ;; juggle ) shift 1; op_juggle "$@" ;; From 163332f368e0f91259243c627d52eb70341c4a3f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 21 May 2025 12:50:08 -0700 Subject: [PATCH 128/142] pandad: fix premature USB panda recovery (#35287) Update pandad.py --- selfdrive/pandad/pandad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index fd6668feba..b33ffb6473 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -87,7 +87,7 @@ def main() -> None: # TODO: remove this in the next AGNOS # wait until USB is up before counting - if time.monotonic() < 25.: + if time.monotonic() < 35.: no_internal_panda_count = 0 # Handle missing internal panda From 9c40c487292956618c58a4e77b4c9d825d5ae91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 22 May 2025 03:55:19 +0200 Subject: [PATCH 129/142] Revert "Enable online lag learning (#34968)" (#35322) * Revert "Enable online lag learning (#34968)" * Update ref commit --- selfdrive/locationd/torqued.py | 7 +++---- selfdrive/modeld/modeld.py | 4 ++-- selfdrive/test/process_replay/process_replay.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 5ed86e457f..fcaa19df73 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -52,7 +52,7 @@ class TorqueBuckets(PointBuckets): class TorqueEstimator(ParameterEstimator): def __init__(self, CP, decimated=False, track_all_points=False): self.hist_len = int(HISTORY / DT_MDL) - self.lag = 0.0 + self.lag = CP.steerActuatorDelay + .2 # from controlsd self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters if decimated: self.min_bucket_points = MIN_BUCKET_POINTS / 10 @@ -175,8 +175,7 @@ class TorqueEstimator(ParameterEstimator): self.raw_points["steer_override"].append(msg.steeringPressed) elif which == "liveCalibration": self.calibrator.feed_live_calib(msg) - elif which == "liveDelay": - self.lag = msg.lateralDelay + # calculate lateral accel from past steering torque elif which == "livePose": if len(self.raw_points['steer_torque']) == self.hist_len: @@ -242,7 +241,7 @@ def main(demo=False): config_realtime_process([0, 1, 2, 3], 5) pm = messaging.PubMaster(['liveTorqueParameters']) - sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose', 'liveDelay'], poll='livePose') + sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose'], poll='livePose') params = Params() estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 1e3d1782e6..96520a4b15 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -229,7 +229,7 @@ def main(demo=False): # messaging pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"]) - sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) + sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"]) publish_state = PublishState() params = Params() @@ -256,6 +256,7 @@ def main(demo=False): # TODO this needs more thought, use .2s extra for now to estimate other delays # TODO Move smooth seconds to action function + lat_delay = CP.steerActuatorDelay + .2 + LAT_SMOOTH_SECONDS long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS prev_action = log.ModelDataV2.Action() @@ -299,7 +300,6 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) - lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32) if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8de2e60055..d87a330fdd 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -564,7 +564,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="torqued", - pubs=["livePose", "liveCalibration", "liveDelay", "carState", "carControl", "carOutput"], + pubs=["livePose", "liveCalibration", "carState", "carControl", "carOutput"], subs=["liveTorqueParameters"], ignore=["logMonoTime"], init_callback=get_car_params_callback, @@ -573,7 +573,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"], + pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState", "carControl"], subs=["modelV2", "drivingModelData", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index f916e7b306..eef00985f3 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -8e57f774d0e8302e1bf1834f84720bb1f9211ae3 \ No newline at end of file +a6c19d1dacbebccee6d14313314e68c5140b836c \ No newline at end of file From 3f53133da5a268c6e32f1abd5f6daaf225d9e66e Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Wed, 21 May 2025 19:58:18 -0700 Subject: [PATCH 130/142] AGNOS 12.2 (#35317) * agnos 12.2 * non-staging --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 52 ++++++++-------- system/hardware/tici/all-partitions.json | 76 ++++++++++++------------ 3 files changed, 65 insertions(+), 65 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 73a7a89789..6f31fcf776 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="12.1" + export AGNOS_VERSION="12.2" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index a415851a10..e209613f42 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,25 +1,25 @@ [ { "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c.img.xz", - "hash": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c", - "hash_raw": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1.img.xz", + "hash": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1", + "hash_raw": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "d35a86e7b8ddd9279b513a6f27da1521aa0f89fb93987ea74d57d0f0bbbbd247" + "ondevice_hash": "003a17ab1be68a696f7efe4c9938e8be511d4aacfc2f3211fc896bdc1681d089" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b.img.xz", - "hash": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b", - "hash_raw": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535.img.xz", + "hash": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535", + "hash_raw": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "623f1568072ee2d687ba8449a3d894c1c83dc4131b2e79eff35696885f70a419" + "ondevice_hash": "2a855dd636cc94718b64bea83a44d0a31741ecaa8f72a63613ff348ec7404091" }, { "name": "abl", @@ -34,50 +34,50 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5.img.xz", - "hash": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5", - "hash_raw": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5", + "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz", + "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", + "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "bf74feca486f650589f6b7c90eab73274e35a68b5e00bfc1de0ed5f5484d4b3d" + "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180" }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180.img.xz", - "hash": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180", - "hash_raw": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz", + "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", + "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "70f682b59ca0fe2f197d1486bd8be7b9b7e560798ad40ddef83b9f0a2f497938" + "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f" }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425.img.xz", - "hash": "3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425", - "hash_raw": "3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425", + "url": "https://commadist.azureedge.net/agnosupdate/boot-eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968.img.xz", + "hash": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968", + "hash_raw": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968", "size": 18479104, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "2075104847d1c96a06f07e85efb9f48d0e792d75a059047eae7ba4b463ffeadf" + "ondevice_hash": "800868bd9d340f1fdf8340924caca374409624658324607621663fc5c7d10d4f" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7.img.xz", - "hash": "cb9bfde1e995b97f728f5d5ad8d7a0f7a01544db5d138ead9b2350f222640939", - "hash_raw": "b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7", + "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img.xz", + "hash": "226794914e9d157b34cc86f1fbe1f2827a9a9919dbe560021b61573a7e5d3101", + "hash_raw": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "e92a1f34158c60364c8d47b8ebbb6e59edf8d4865cd5edfeb2355d6f54f617fc", + "ondevice_hash": "236890f04ee21b7eb92a59bc47971bd96cad5a4381ed8935eb4be0cb5a4cf48b", "alt": { - "hash": "b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7", - "url": "https://commadist.azureedge.net/agnosupdate/system-b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7.img", + "hash": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1", + "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img", "size": 5368709120 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index be303b7f90..7e39d275cc 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -130,25 +130,25 @@ }, { "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c.img.xz", - "hash": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c", - "hash_raw": "468f1ad6ab55e198647ff9191f91bd2918db9c0a3e27bae5673b4c5575c1254c", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1.img.xz", + "hash": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1", + "hash_raw": "6710967ca9701f205d7ab19c3a9b0dd2f547e65b3d96048b7c2b03755aafa0f1", "size": 3282256, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "d35a86e7b8ddd9279b513a6f27da1521aa0f89fb93987ea74d57d0f0bbbbd247" + "ondevice_hash": "003a17ab1be68a696f7efe4c9938e8be511d4aacfc2f3211fc896bdc1681d089" }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b.img.xz", - "hash": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b", - "hash_raw": "92b675dc2862ed15c732d91d9eb307d7e852e349217db8bee8f8829db543686b", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535.img.xz", + "hash": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535", + "hash_raw": "63922cfbfdf4ab87986c4ba8f3a4df5bf28414b3f71a29ec5947336722215535", "size": 98124, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "623f1568072ee2d687ba8449a3d894c1c83dc4131b2e79eff35696885f70a419" + "ondevice_hash": "2a855dd636cc94718b64bea83a44d0a31741ecaa8f72a63613ff348ec7404091" }, { "name": "abl", @@ -163,14 +163,14 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5.img.xz", - "hash": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5", - "hash_raw": "f0fcf7611d0890a72984f15a516dd37fa532dfcb70d428a8406838cf74ce23d5", + "url": "https://commadist.azureedge.net/agnosupdate/aop-21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9.img.xz", + "hash": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", + "hash_raw": "21370172e590bd4ea907a558bcd6df20dc7a6c7d38b8e62fdde18f4a512ba9e9", "size": 184364, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "bf74feca486f650589f6b7c90eab73274e35a68b5e00bfc1de0ed5f5484d4b3d" + "ondevice_hash": "c1be2f4aac5b3af49b904b027faec418d05efd7bd5144eb4fdfcba602bcf2180" }, { "name": "bluetooth", @@ -207,14 +207,14 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180.img.xz", - "hash": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180", - "hash_raw": "225b24ea7b1d2fee7f7d2da21386920ddacac2e33e9e938168436292f4eae180", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620.img.xz", + "hash": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", + "hash_raw": "d7d7e52963bbedbbf8a7e66847579ca106a0a729ce2cf60f4b8d8ea4b535d620", "size": 40336, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "70f682b59ca0fe2f197d1486bd8be7b9b7e560798ad40ddef83b9f0a2f497938" + "ondevice_hash": "17b229668b20305ff8fa3cd5f94716a3aaa1e5bf9d1c24117eff7f2f81ae719f" }, { "name": "devinfo", @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425.img.xz", - "hash": "3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425", - "hash_raw": "3d8e848796924081f5a6b3d745808b1117ae2ec41c03f2d41ee2e75633bd6425", + "url": "https://commadist.azureedge.net/agnosupdate/boot-eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968.img.xz", + "hash": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968", + "hash_raw": "eb0dd0a8ffdc1c03aa28d055788d70be885a00f523b552d7acf79b954838e968", "size": 18479104, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "2075104847d1c96a06f07e85efb9f48d0e792d75a059047eae7ba4b463ffeadf" + "ondevice_hash": "800868bd9d340f1fdf8340924caca374409624658324607621663fc5c7d10d4f" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7.img.xz", - "hash": "cb9bfde1e995b97f728f5d5ad8d7a0f7a01544db5d138ead9b2350f222640939", - "hash_raw": "b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7", + "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img.xz", + "hash": "226794914e9d157b34cc86f1fbe1f2827a9a9919dbe560021b61573a7e5d3101", + "hash_raw": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1", "size": 5368709120, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "e92a1f34158c60364c8d47b8ebbb6e59edf8d4865cd5edfeb2355d6f54f617fc", + "ondevice_hash": "236890f04ee21b7eb92a59bc47971bd96cad5a4381ed8935eb4be0cb5a4cf48b", "alt": { - "hash": "b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7", - "url": "https://commadist.azureedge.net/agnosupdate/system-b22bd239c6f9527596fd49b98b7d521a563f99b90953ce021ee36567498e99c7.img", + "hash": "4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1", + "url": "https://commadist.azureedge.net/agnosupdate/system-4c3fa88833a8a3876653fa53a658769ecc64be8fc6f6a28ce520ef79bf3061c1.img", "size": 5368709120 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-4bb7239f7e82c846e4d2584c0c433f03c582a80950de4094e6c190563d6d84ac.img.xz", - "hash": "b18001a2a87caa070fabf6321f8215ac353d6444564e3f86329b4dccc039ce54", - "hash_raw": "4bb7239f7e82c846e4d2584c0c433f03c582a80950de4094e6c190563d6d84ac", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f6d24876234f6bea9cc753892eea99ac4b0c8646e93b93d76fc5dddce67347f1.img.xz", + "hash": "2485459e9fbfc0d88a59a017bfa193d97b80afed30d6f59db711e53f62a21c72", + "hash_raw": "f6d24876234f6bea9cc753892eea99ac4b0c8646e93b93d76fc5dddce67347f1", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "15ce16f2349d5b4d5fec6ad1e36222b1ae744ed10b8930bc9af75bd244dccb3c" + "ondevice_hash": "893cfb3e95d6025b9fa6a5c8c3b97bf2abc4ff036c3da846a07536653dbb3a1d" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-e36b59bf9ff755b6ca488df2ba1e20da8f7dab6b8843129f3fdcccd7ff2ff7d8.img.xz", - "hash": "12682cf54596ab1bd1c2464c4ca85888e4e06b47af5ff7d0432399e9907e2f64", - "hash_raw": "e36b59bf9ff755b6ca488df2ba1e20da8f7dab6b8843129f3fdcccd7ff2ff7d8", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-14f0f02e53ce62a79b337d7b3174dcaffebaa4130264bb5ed9105d942338230b.img.xz", + "hash": "9222aebb280531b7015ee9bab3848dd195567d075b6f37f74d4cd0186685a11f", + "hash_raw": "14f0f02e53ce62a79b337d7b3174dcaffebaa4130264bb5ed9105d942338230b", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "e4df9dea47ff04967d971263d50c17460ef240457e8d814e7c4f409f7493eb8a" + "ondevice_hash": "a5459ec858d39e3d0709a09af430cb644fe640f92e0cd216b138f01401e4e17e" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-fe1d86f5322c675c58b3ae9753a4670abf44a25746bf6ac822aed108bb577282.img.xz", - "hash": "fa471703be0f0647617d183312d5209d23407f1628e4ab0934e6ec54b1a6b263", - "hash_raw": "fe1d86f5322c675c58b3ae9753a4670abf44a25746bf6ac822aed108bb577282", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-180ad331538f20c00218d41591afed3a25bb0f76fa30b1a665cad102cf6c9f7d.img.xz", + "hash": "7fc09317f005676150bfcced43ebb1aa919d854c2511d547a588a35c1122bfe3", + "hash_raw": "180ad331538f20c00218d41591afed3a25bb0f76fa30b1a665cad102cf6c9f7d", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "0b5b2402c9caa1ed7b832818e66580c974251e735bda91f2f226c41499d5616e" + "ondevice_hash": "1a2f2d14c922bd2895073446460a0bd680711a7b14318a4402631635e6d133b3" } ] \ No newline at end of file From c9f3cd5ad2ce223f3ff9bc623453aafc0327c8cd Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 22 May 2025 11:31:50 +0800 Subject: [PATCH 131/142] system/ui: enhance scroll panel with iPhone-like physics and behavior (#35312) * improve scroll panel for iphone-like experience * add comments * increase demo run time for easier testing --- system/ui/lib/scroll_panel.py | 168 ++++++++++++++++++++++++++++------ system/ui/text.py | 2 +- 2 files changed, 139 insertions(+), 31 deletions(-) diff --git a/system/ui/lib/scroll_panel.py b/system/ui/lib/scroll_panel.py index 7dc2fdb917..43111504bb 100644 --- a/system/ui/lib/scroll_panel.py +++ b/system/ui/lib/scroll_panel.py @@ -1,16 +1,23 @@ import pyray as rl from enum import IntEnum +# Scroll constants for smooth scrolling behavior MOUSE_WHEEL_SCROLL_SPEED = 30 -INERTIA_FRICTION = 0.95 # The rate at which the inertia slows down -MIN_VELOCITY = 0.1 # Minimum velocity before stopping the inertia -DRAG_THRESHOLD = 5 # Pixels of movement to consider it a drag, not a click +INERTIA_FRICTION = 0.92 # The rate at which the inertia slows down +MIN_VELOCITY = 0.5 # Minimum velocity before stopping the inertia +DRAG_THRESHOLD = 5 # Pixels of movement to consider it a drag, not a click +BOUNCE_FACTOR = 0.2 # Elastic bounce when scrolling past boundaries +BOUNCE_RETURN_SPEED = 0.15 # How quickly it returns from the bounce +MAX_BOUNCE_DISTANCE = 150 # Maximum distance for bounce effect +FLICK_MULTIPLIER = 1.8 # Multiplier for flick gestures +VELOCITY_HISTORY_SIZE = 5 # Track velocity over multiple frames for smoother motion class ScrollState(IntEnum): IDLE = 0 DRAGGING_CONTENT = 1 DRAGGING_SCROLLBAR = 2 + BOUNCING = 3 class GuiScrollPanel: @@ -22,14 +29,33 @@ class GuiScrollPanel: self._view = rl.Rectangle(0, 0, 0, 0) self._show_vertical_scroll_bar: bool = show_vertical_scroll_bar self._velocity_y = 0.0 # Velocity for inertia - self._is_dragging = False + self._is_dragging: bool = False + self._bounce_offset: float = 0.0 + self._last_frame_time = rl.get_time() + self._velocity_history: list[float] = [] + self._last_drag_time: float = 0.0 + self._content_rect: rl.Rectangle | None = None + self._bounds_rect: rl.Rectangle | None = None def handle_scroll(self, bounds: rl.Rectangle, content: rl.Rectangle) -> rl.Vector2: - mouse_pos = rl.get_mouse_position() + # Store rectangles for reference + self._content_rect = content + self._bounds_rect = bounds - # Handle dragging logic + # Calculate time delta + current_time = rl.get_time() + delta_time = current_time - self._last_frame_time + self._last_frame_time = current_time + + # Prevent large jumps + delta_time = min(delta_time, 0.05) + + mouse_pos = rl.get_mouse_position() + max_scroll_y = max(content.height - bounds.height, 0) + + # Start dragging on mouse press if rl.check_collision_point_rec(mouse_pos, bounds) and rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT): - if self._scroll_state == ScrollState.IDLE: + if self._scroll_state == ScrollState.IDLE or self._scroll_state == ScrollState.BOUNCING: self._scroll_state = ScrollState.DRAGGING_CONTENT if self._show_vertical_scroll_bar: scrollbar_width = rl.gui_get_style(rl.GuiControl.LISTVIEW, rl.GuiListViewProperty.SCROLLBAR_WIDTH) @@ -38,51 +64,133 @@ class GuiScrollPanel: self._scroll_state = ScrollState.DRAGGING_SCROLLBAR self._last_mouse_y = mouse_pos.y - self._start_mouse_y = mouse_pos.y # Record starting position - self._velocity_y = 0.0 # Reset velocity when drag starts - self._is_dragging = False # Reset dragging flag + self._start_mouse_y = mouse_pos.y + self._last_drag_time = current_time + self._velocity_history = [] + self._velocity_y = 0.0 + self._bounce_offset = 0.0 + self._is_dragging = False - if self._scroll_state != ScrollState.IDLE: + # Handle active dragging + if self._scroll_state == ScrollState.DRAGGING_CONTENT or self._scroll_state == ScrollState.DRAGGING_SCROLLBAR: if rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT): delta_y = mouse_pos.y - self._last_mouse_y - # Check if movement exceeds the drag threshold + # Track velocity for inertia + time_since_last_drag = current_time - self._last_drag_time + if time_since_last_drag > 0: + drag_velocity = delta_y / time_since_last_drag / 60.0 + self._velocity_history.append(drag_velocity) + + if len(self._velocity_history) > VELOCITY_HISTORY_SIZE: + self._velocity_history.pop(0) + + self._last_drag_time = current_time + + # Detect actual dragging total_drag = abs(mouse_pos.y - self._start_mouse_y) if total_drag > DRAG_THRESHOLD: self._is_dragging = True if self._scroll_state == ScrollState.DRAGGING_CONTENT: + # Add resistance at boundaries + if (self._offset.y > 0 and delta_y > 0) or (self._offset.y < -max_scroll_y and delta_y < 0): + delta_y *= BOUNCE_FACTOR + self._offset.y += delta_y elif self._scroll_state == ScrollState.DRAGGING_SCROLLBAR: - delta_y = -delta_y + scroll_ratio = content.height / bounds.height + self._offset.y -= delta_y * scroll_ratio self._last_mouse_y = mouse_pos.y - self._velocity_y = delta_y # Update velocity during drag - elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): - self._scroll_state = ScrollState.IDLE - # Handle mouse wheel scrolling + elif rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): + # Calculate flick velocity + if self._velocity_history: + total_weight = 0 + weighted_velocity = 0.0 + + for i, v in enumerate(self._velocity_history): + weight = i + 1 + weighted_velocity += v * weight + total_weight += weight + + if total_weight > 0: + avg_velocity = weighted_velocity / total_weight + self._velocity_y = avg_velocity * FLICK_MULTIPLIER + + # Check bounds + if self._offset.y > 0 or self._offset.y < -max_scroll_y: + self._scroll_state = ScrollState.BOUNCING + else: + self._scroll_state = ScrollState.IDLE + + # Handle mouse wheel wheel_move = rl.get_mouse_wheel_move() - if self._show_vertical_scroll_bar: - self._offset.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20) - rl.gui_scroll_panel(bounds, rl.ffi.NULL, content, self._offset, self._view) - else: - self._offset.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED + if wheel_move != 0: + self._velocity_y = 0.0 + + if self._show_vertical_scroll_bar: + self._offset.y += wheel_move * (MOUSE_WHEEL_SCROLL_SPEED - 20) + rl.gui_scroll_panel(bounds, rl.ffi.NULL, content, self._offset, self._view) + else: + self._offset.y += wheel_move * MOUSE_WHEEL_SCROLL_SPEED + + if self._offset.y > 0 or self._offset.y < -max_scroll_y: + self._scroll_state = ScrollState.BOUNCING # Apply inertia (continue scrolling after mouse release) if self._scroll_state == ScrollState.IDLE: - self._offset.y += self._velocity_y - self._velocity_y *= INERTIA_FRICTION # Slow down velocity over time + if abs(self._velocity_y) > MIN_VELOCITY: + self._offset.y += self._velocity_y + self._velocity_y *= INERTIA_FRICTION - # Stop scrolling when velocity is low - if abs(self._velocity_y) < MIN_VELOCITY: + if self._offset.y > 0 or self._offset.y < -max_scroll_y: + self._scroll_state = ScrollState.BOUNCING + else: self._velocity_y = 0.0 - # Ensure scrolling doesn't go beyond bounds - max_scroll_y = max(content.height - bounds.height, 0) - self._offset.y = max(min(self._offset.y, 0), -max_scroll_y) + # Handle bouncing effect + elif self._scroll_state == ScrollState.BOUNCING: + target_y = 0.0 + if self._offset.y < -max_scroll_y: + target_y = -max_scroll_y + + distance = target_y - self._offset.y + bounce_step = distance * BOUNCE_RETURN_SPEED + self._offset.y += bounce_step + self._velocity_y *= INERTIA_FRICTION * 0.8 + + if abs(distance) < 0.5 and abs(self._velocity_y) < MIN_VELOCITY: + self._offset.y = target_y + self._velocity_y = 0.0 + self._scroll_state = ScrollState.IDLE + + # Limit bounce distance + if self._scroll_state != ScrollState.DRAGGING_CONTENT: + if self._offset.y > MAX_BOUNCE_DISTANCE: + self._offset.y = MAX_BOUNCE_DISTANCE + elif self._offset.y < -(max_scroll_y + MAX_BOUNCE_DISTANCE): + self._offset.y = -(max_scroll_y + MAX_BOUNCE_DISTANCE) return self._offset def is_click_valid(self) -> bool: - return self._scroll_state == ScrollState.IDLE and not self._is_dragging and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) + # Check if this is a click rather than a drag + return ( + self._scroll_state == ScrollState.IDLE + and not self._is_dragging + and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) + ) + + def get_normalized_scroll_position(self) -> float: + """Returns the current scroll position as a value from 0.0 to 1.0""" + if not self._content_rect or not self._bounds_rect: + return 0.0 + + max_scroll_y = max(self._content_rect.height - self._bounds_rect.height, 0) + if max_scroll_y == 0: + return 0.0 + + normalized = -self._offset.y / max_scroll_y + return max(0.0, min(1.0, normalized)) diff --git a/system/ui/text.py b/system/ui/text.py index 33e8167c64..82e64d836f 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -88,4 +88,4 @@ class TextWindow(BaseWindow[TextWindowRenderer]): if __name__ == "__main__": with TextWindow(DEMO_TEXT): - time.sleep(5) + time.sleep(30) From f38a98fc0932abc9c6889528ae558132e2b96fc5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 21 May 2025 20:45:47 -0700 Subject: [PATCH 132/142] Metered Wi-Fi toggle (#35293) * draft * here too * fixes * fix * ugh more fix, wifiManager is craze * more * be optimistic and let refresh happen naturally, the immediate refresh causes some paths/active connect to temporarily be unavailable selfdrive/ui/qt/network/wifi_manager.cc: DBus call error: "Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the re ply timeout expired, or the network connection was broken." selfdrive/ui/qt/network/wifi_manager.cc: DBus call error: "Object path cannot be empty" * nm is slow -- it takes 2s to commit to disk, and dbus errors are raised if you try again while previous is running (this is an ubuntu 24.04 bug) * nice experience * rm * minor * clean up * self-explanatory * rename * rm this too * revert * draft * Revert "draft" This reverts commit 15283d977880fc60b8f9732772256e8337d6ac8e. * Reapply "draft" This reverts commit 8629921b0086ca71b88746d77ec7b8d3af3cd289. * rm colors * trinary, bit more code * choose default when disabled * only if enabling, wait for disable as normal * remove original binary toggle * clean up * collapse * clean up wifimanager * update comment * lite is a word --- pyproject.toml | 2 +- selfdrive/ui/qt/network/networking.cc | 46 ++++++++++++++++++++++--- selfdrive/ui/qt/network/networking.h | 3 +- selfdrive/ui/qt/network/wifi_manager.cc | 39 +++++++++++++++++++++ selfdrive/ui/qt/network/wifi_manager.h | 7 ++++ selfdrive/ui/qt/widgets/controls.h | 4 +++ 6 files changed, 94 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 464b605f86..b9a9fcab55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -169,7 +169,7 @@ testpaths = [ [tool.codespell] quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here -ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl" +ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.ts, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*" diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 93f7ff3f21..bb914b6449 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -173,14 +173,36 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid }); list->addItem(editApnButton); - // Metered toggle + // Cellular metered toggle (prime lite or none) const bool metered = params.getBool("GsmMetered"); - meteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered); - QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) { + cellularMeteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered cellular connection"), "", metered); + QObject::connect(cellularMeteredToggle, &SshToggle::toggleFlipped, [=](bool state) { params.putBool("GsmMetered", state); wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state); }); - list->addItem(meteredToggle); + list->addItem(cellularMeteredToggle); + + // Wi-Fi metered toggle + std::vector metered_button_texts{tr("default"), tr("metered"), tr("unmetered")}; + wifiMeteredToggle = new MultiButtonControl(tr("Wi-Fi Network Metered"), tr("Prevent large data uploads when on a metered Wi-FI connection"), "", metered_button_texts); + QObject::connect(wifiMeteredToggle, &MultiButtonControl::buttonClicked, [=](int id) { + wifiMeteredToggle->setEnabled(false); + MeteredType metered = MeteredType::UNKNOWN; + if (id == NM_METERED_YES) { + metered = MeteredType::YES; + } else if (id == NM_METERED_NO) { + metered = MeteredType::NO; + } + auto pending_call = wifi->setCurrentNetworkMetered(metered); + if (pending_call) { + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(*pending_call); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=]() { + refresh(); + watcher->deleteLater(); + }); + } + }); + list->addItem(wifiMeteredToggle); // Hidden Network hiddenNetworkButton = new ButtonControl(tr("Hidden Network"), tr("CONNECT")); @@ -211,18 +233,32 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid void AdvancedNetworking::setGsmVisible(bool visible) { roamingToggle->setVisible(visible); editApnButton->setVisible(visible); - meteredToggle->setVisible(visible); + cellularMeteredToggle->setVisible(visible); } void AdvancedNetworking::refresh() { ipLabel->setText(wifi->ipv4_address); tetheringToggle->setEnabled(true); + + if (wifi->isTetheringEnabled() || wifi->ipv4_address == "") { + wifiMeteredToggle->setEnabled(false); + wifiMeteredToggle->setCheckedButton(0); + } else if (wifi->ipv4_address != "") { + MeteredType metered = wifi->currentNetworkMetered(); + wifiMeteredToggle->setEnabled(true); + wifiMeteredToggle->setCheckedButton(static_cast(metered)); + } + update(); } void AdvancedNetworking::toggleTethering(bool enabled) { wifi->setTetheringEnabled(enabled); tetheringToggle->setEnabled(false); + if (enabled) { + wifiMeteredToggle->setEnabled(false); + wifiMeteredToggle->setCheckedButton(0); + } } // WifiUI functions diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 4fd604039b..0bdf64e459 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -65,7 +65,8 @@ private: ToggleControl* roamingToggle; ButtonControl* editApnButton; ButtonControl* hiddenNetworkButton; - ToggleControl* meteredToggle; + ToggleControl* cellularMeteredToggle; + MultiButtonControl* wifiMeteredToggle; WifiManager* wifi = nullptr; Params params; diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 3d4bb9e7d1..d4ad8974b0 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -353,6 +353,7 @@ void WifiManager::activateModemConnection(const QDBusObjectPath &path) { } // function matches tici/hardware.py +// FIXME: it can mistakenly show CELL when connected to WIFI NetworkType WifiManager::currentNetworkType() { auto primary_conn = call(NM_DBUS_PATH, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE, "PrimaryConnection"); auto primary_type = call(primary_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type"); @@ -372,6 +373,44 @@ NetworkType WifiManager::currentNetworkType() { return NetworkType::NONE; } +MeteredType WifiManager::currentNetworkMetered() { + MeteredType metered = MeteredType::UNKNOWN; + for (const auto &active_conn : getActiveConnections()) { + QString type = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type"); + if (type == "802-11-wireless") { + QDBusObjectPath conn = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Connection"); + if (!conn.path().isEmpty()) { + Connection settings = getConnectionSettings(conn); + int metered_prop = settings.value("connection").value("metered").toInt(); + if (metered_prop == NM_METERED_YES) { + metered = MeteredType::YES; + } else if (metered_prop == NM_METERED_NO) { + metered = MeteredType::NO; + } + } + break; + } + } + return metered; +} + +std::optional WifiManager::setCurrentNetworkMetered(MeteredType metered) { + for (const auto &active_conn : getActiveConnections()) { + QString type = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Type"); + if (type == "802-11-wireless") { + if (!isTetheringEnabled()) { + QDBusObjectPath conn = call(active_conn.path(), NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_ACTIVE_CONNECTION, "Connection"); + if (!conn.path().isEmpty()) { + Connection settings = getConnectionSettings(conn); + settings["connection"]["metered"] = static_cast(metered); + return asyncCall(conn.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "Update", QVariant::fromValue(settings)); + } + } + } + } + return std::nullopt; +} + void WifiManager::updateGsmSettings(bool roaming, QString apn, bool metered) { if (!lteConnectionPath.path().isEmpty()) { bool changes = false; diff --git a/selfdrive/ui/qt/network/wifi_manager.h b/selfdrive/ui/qt/network/wifi_manager.h index e5f79c5149..cab932a388 100644 --- a/selfdrive/ui/qt/network/wifi_manager.h +++ b/selfdrive/ui/qt/network/wifi_manager.h @@ -22,6 +22,11 @@ enum class NetworkType { CELL, ETHERNET }; +enum class MeteredType { + UNKNOWN, + YES, + NO +}; typedef QMap Connection; typedef QVector IpConfig; @@ -53,6 +58,8 @@ public: bool isKnownConnection(const QString &ssid); std::optional activateWifiConnection(const QString &ssid); NetworkType currentNetworkType(); + MeteredType currentNetworkMetered(); + std::optional setCurrentNetworkMetered(MeteredType metered); void updateGsmSettings(bool roaming, QString apn, bool metered); void connect(const Network &ssid, const bool is_hidden = false, const QString &password = {}, const QString &username = {}); diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index ca93110e5b..3342de5324 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -204,6 +204,9 @@ public: QPushButton:checked:enabled { background-color: #33Ab4C; } + QPushButton:checked:disabled { + background-color: #9933Ab4C; + } QPushButton:disabled { color: #33E4E4E4; } @@ -214,6 +217,7 @@ public: for (int i = 0; i < button_texts.size(); i++) { QPushButton *button = new QPushButton(button_texts[i], this); button->setCheckable(true); + button->setChecked(i == 0); button->setStyleSheet(style); button->setMinimumWidth(minimum_button_width); hlayout->addWidget(button); From fdfba3f9f7792df5725260023d18f93bd503b094 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 23 May 2025 02:23:50 +0800 Subject: [PATCH 133/142] system/ui: update camera view shader to support rendering on device (#35326) update shader to support rendering on device --- system/ui/widgets/cameraview.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/system/ui/widgets/cameraview.py b/system/ui/widgets/cameraview.py index 841440ae3a..09fe306176 100644 --- a/system/ui/widgets/cameraview.py +++ b/system/ui/widgets/cameraview.py @@ -2,20 +2,41 @@ import pyray as rl from msgq.visionipc import VisionIpcClient, VisionStreamType from openpilot.system.ui.lib.application import gui_app + +VERTEX_SHADER = """ +#version 300 es +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; +uniform mat4 mvp; +out vec2 fragTexCoord; +out vec4 fragColor; +void main() { + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + gl_Position = mvp * vec4(vertexPosition, 1.0); +} +""" + FRAME_FRAGMENT_SHADER = """ -#version 330 core -in vec2 fragTexCoord; uniform sampler2D texture0, texture1; out vec4 fragColor; +#version 300 es +precision mediump float; +in vec2 fragTexCoord; +uniform sampler2D texture0; +uniform sampler2D texture1; +out vec4 fragColor; void main() { float y = texture(texture0, fragTexCoord).r; vec2 uv = texture(texture1, fragTexCoord).ra - 0.5; fragColor = vec4(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x, 1.0); -}""" - +} +""" class CameraView: def __init__(self, name: str, stream_type: VisionStreamType): self.client = VisionIpcClient(name, stream_type, False) - self.shader = rl.load_shader_from_memory(rl.ffi.NULL, FRAME_FRAGMENT_SHADER) + self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) self.texture_y: rl.Texture | None = None self.texture_uv: rl.Texture | None = None self.frame = None From 9b642ed01664b920bdac0e238720c27e70562b0d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 22 May 2025 17:49:27 -0700 Subject: [PATCH 134/142] bump opendbc (#35323) * bump * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 0676642f40..afb464bd7a 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0676642f4029b02758405b3052d1ef8ad2a5528d +Subproject commit afb464bd7a3134ab5511d26c0ca9060cb2793ed5 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index eef00985f3..0ca95ec5fa 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -a6c19d1dacbebccee6d14313314e68c5140b836c \ No newline at end of file +280cd05fe44e47474d04f2cf7435fb0000a562e5 \ No newline at end of file From 312658756dbe16b802a90b2e9e9d8d8dcc868779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 23 May 2025 03:33:55 +0200 Subject: [PATCH 135/142] lagd: handle ambiguous cases (#35257) * Mechanism to handle ambiguous estimates * Use it * Fix static * Fix * Comment * Comment v2 * Use quantile instead * Fix * Make it better * Make it confidence * Make it a constant * min_confidence as arg * Make the test signal more like the real one * Relax sample requirements * Use constant * Reduce confidence --- selfdrive/locationd/lagd.py | 33 ++++++++++++++++++++------- selfdrive/locationd/test/test_lagd.py | 4 ++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index b4f4e1f663..4e207b4882 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -16,17 +16,20 @@ from openpilot.selfdrive.locationd.helpers import PoseCalibrator, Pose, fft_next BLOCK_SIZE = 100 BLOCK_NUM = 50 BLOCK_NUM_NEEDED = 5 -MOVING_WINDOW_SEC = 300.0 +MOVING_WINDOW_SEC = 60.0 MIN_OKAY_WINDOW_SEC = 25.0 MIN_RECOVERY_BUFFER_SEC = 2.0 MIN_VEGO = 15.0 -MIN_ABS_YAW_RATE = np.radians(1.0) +MIN_ABS_YAW_RATE = 0.0 MAX_YAW_RATE_SANITY_CHECK = 1.0 MIN_NCC = 0.95 MAX_LAG = 1.0 MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 +MIN_CONFIDENCE = 0.7 +CORR_BORDER_OFFSET = 5 +LAG_CANDIDATE_CORR_THRESHOLD = 0.9 def masked_normalized_cross_correlation(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, n: int): @@ -154,7 +157,7 @@ class LateralLagEstimator: block_count: int = BLOCK_NUM, min_valid_block_count: int = BLOCK_NUM_NEEDED, block_size: int = BLOCK_SIZE, window_sec: float = MOVING_WINDOW_SEC, okay_window_sec: float = MIN_OKAY_WINDOW_SEC, min_recovery_buffer_sec: float = MIN_RECOVERY_BUFFER_SEC, min_vego: float = MIN_VEGO, min_yr: float = MIN_ABS_YAW_RATE, min_ncc: float = MIN_NCC, - max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF): + max_lat_accel: float = MAX_LAT_ACCEL, max_lat_accel_diff: float = MAX_LAT_ACCEL_DIFF, min_confidence: float = MIN_CONFIDENCE): self.dt = dt self.window_sec = window_sec self.okay_window_sec = okay_window_sec @@ -166,6 +169,7 @@ class LateralLagEstimator: self.min_vego = min_vego self.min_yr = min_yr self.min_ncc = min_ncc + self.min_confidence = min_confidence self.max_lat_accel = max_lat_accel self.max_lat_accel_diff = max_lat_accel_diff @@ -292,14 +296,14 @@ class LateralLagEstimator: new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) - delay, corr = self.actuator_delay(desired, actual, okay, self.dt, MAX_LAG) - if corr < self.min_ncc or not is_valid: + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MAX_LAG) + if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return self.block_avg.update(delay) self.last_estimate_t = self.t - def actuator_delay(self, expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, max_lag: float) -> tuple[float, float]: + def actuator_delay(self, expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) max_lag_samples = int(max_lag / dt) padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) @@ -307,13 +311,26 @@ class LateralLagEstimator: ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) # only consider lags from 0 to max_lag - roi_ncc = ncc[len(expected_sig) - 1: len(expected_sig) - 1 + max_lag_samples] + roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + max_lag_samples] + extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] + roi_ncc = ncc[roi] + extended_roi_ncc = ncc[extended_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt - return lag, corr + # to estimate lag confidence, gather all high-correlation candidates and see how spread they are + # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case + ncc_thresh = (roi_ncc.max() - roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + roi_ncc.min() + good_lag_candidate_mask = extended_roi_ncc >= ncc_thresh + good_lag_candidate_edges = np.diff(good_lag_candidate_mask.astype(int), prepend=0, append=0) + starts, ends = np.where(good_lag_candidate_edges == 1)[0], np.where(good_lag_candidate_edges == -1)[0] - 1 + run_idx = np.searchsorted(starts, max_corr_index + CORR_BORDER_OFFSET, side='right') - 1 + width = ends[run_idx] - starts[run_idx] + 1 + confidence = np.clip(1 - width * dt, 0, 1) + + return lag, corr, confidence def retrieve_initial_lag(params: Params, CP: car.CarParams): diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index 13aea60a26..b805f1759d 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -23,8 +23,8 @@ def process_messages(mocker, estimator, lag_frames, n_frames, vego=20.0, rejecti for i in range(n_frames): t = i * estimator.dt - desired_la = np.cos(t) * 0.1 - actual_la = np.cos(t - lag_frames * estimator.dt) * 0.1 + desired_la = np.cos(10 * t) * 0.1 + actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.1 # if sample is masked out, set it to desired value (no lag) rejected = random.uniform(0, 1) < rejection_threshold From 9622427044253bacfa314e47fa0808779252116e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 May 2025 16:32:19 -0700 Subject: [PATCH 136/142] car interfaces: improve steering pressed signal (#35333) * bump * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index afb464bd7a..c495fa822c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit afb464bd7a3134ab5511d26c0ca9060cb2793ed5 +Subproject commit c495fa822c845b0bfc6e1511ced3425d111f3fe4 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0ca95ec5fa..5f5cb89089 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -280cd05fe44e47474d04f2cf7435fb0000a562e5 \ No newline at end of file +5c8f6c360b66f269a24d519e699addf9e9bcaefe \ No newline at end of file From 70644e23172e6952da70978ba0fb91d96e7ae195 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 May 2025 16:54:50 -0700 Subject: [PATCH 137/142] VW: low steer speed alert in CarState (#35331) * move to opendbc * func * clean up * bump * move cmt * bump --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 9 --------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index c495fa822c..0c8b12328e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c495fa822c845b0bfc6e1511ced3425d111f3fe4 +Subproject commit 0c8b12328ec4fcb7481806f62cd90310573d4c0c diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index c5edb484af..ed3a0cb9d8 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -2,7 +2,6 @@ from cereal import car, log import cereal.messaging as messaging from opendbc.car import DT_CTRL, structs from opendbc.car.interfaces import MAX_CTRL_SPEED -from opendbc.car.volkswagen.values import CarControllerParams as VWCarControllerParams from openpilot.selfdrive.selfdrived.events import Events @@ -112,14 +111,6 @@ class CarSpecificEvents: events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], pcm_enable=self.CP.pcmCruise) - # Low speed steer alert hysteresis logic - if (self.CP.minSteerSpeed - 1e-3) > VWCarControllerParams.DEFAULT_MIN_STEER_SPEED and CS.vEgo < (self.CP.minSteerSpeed + 1.): - self.low_speed_alert = True - elif CS.vEgo > (self.CP.minSteerSpeed + 2.): - self.low_speed_alert = False - if self.low_speed_alert: - events.add(EventName.belowSteerSpeed) - if self.CP.openpilotLongitudinalControl: if CS.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) From ce8ea163037fa3da32df17062e0f7d94eaa366fb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 23 May 2025 18:50:20 -0700 Subject: [PATCH 138/142] update release notes --- RELEASES.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 5730877f94..acdb709c9b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,13 +1,11 @@ -Version 0.9.9 (2025-05-15) +Version 0.9.9 (2025-05-23) ======================== * New driving model - * New training architecture supervised by MLSIM -* Steering actuator delay is now learned online + * New training architecture using parts from MLSIM +* Steering actuation delay is now learned online +* Hyundai Nexo 2021 support thanks to sunnyhaibin! * Tesla Model 3 and Y support thanks to lukasloetkolben! * Lexus RC 2023 support thanks to nelsonjchen! -* Coming soon - * New Honda models - * Bigger vision model Version 0.9.8 (2025-02-28) ======================== From 97ad175243a01f89151b0156afac3a2fa2b3be3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Sat, 24 May 2025 04:31:17 +0200 Subject: [PATCH 139/142] Reapply "Enable online lag learning (#34968)" (#35327) * Revert "Revert "Enable online lag learning (#34968)" (#35322)" This reverts commit 9c40c487292956618c58a4e77b4c9d825d5ae91b. * Update ref commit * Update ref * Update ref commit --- selfdrive/locationd/torqued.py | 7 ++++--- selfdrive/modeld/modeld.py | 4 ++-- selfdrive/test/process_replay/process_replay.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index fcaa19df73..5ed86e457f 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -52,7 +52,7 @@ class TorqueBuckets(PointBuckets): class TorqueEstimator(ParameterEstimator): def __init__(self, CP, decimated=False, track_all_points=False): self.hist_len = int(HISTORY / DT_MDL) - self.lag = CP.steerActuatorDelay + .2 # from controlsd + self.lag = 0.0 self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters if decimated: self.min_bucket_points = MIN_BUCKET_POINTS / 10 @@ -175,7 +175,8 @@ class TorqueEstimator(ParameterEstimator): self.raw_points["steer_override"].append(msg.steeringPressed) elif which == "liveCalibration": self.calibrator.feed_live_calib(msg) - + elif which == "liveDelay": + self.lag = msg.lateralDelay # calculate lateral accel from past steering torque elif which == "livePose": if len(self.raw_points['steer_torque']) == self.hist_len: @@ -241,7 +242,7 @@ def main(demo=False): config_realtime_process([0, 1, 2, 3], 5) pm = messaging.PubMaster(['liveTorqueParameters']) - sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose'], poll='livePose') + sm = messaging.SubMaster(['carControl', 'carOutput', 'carState', 'liveCalibration', 'livePose', 'liveDelay'], poll='livePose') params = Params() estimator = TorqueEstimator(messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 96520a4b15..1e3d1782e6 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -229,7 +229,7 @@ def main(demo=False): # messaging pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"]) - sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl"]) + sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) publish_state = PublishState() params = Params() @@ -256,7 +256,6 @@ def main(demo=False): # TODO this needs more thought, use .2s extra for now to estimate other delays # TODO Move smooth seconds to action function - lat_delay = CP.steerActuatorDelay + .2 + LAT_SMOOTH_SECONDS long_delay = CP.longitudinalActuatorDelay + LONG_SMOOTH_SECONDS prev_action = log.ModelDataV2.Action() @@ -300,6 +299,7 @@ def main(demo=False): is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId v_ego = max(sm["carState"].vEgo, 0.) + lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS lateral_control_params = np.array([v_ego, lat_delay], dtype=np.float32) if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index d87a330fdd..8de2e60055 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -564,7 +564,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="torqued", - pubs=["livePose", "liveCalibration", "carState", "carControl", "carOutput"], + pubs=["livePose", "liveCalibration", "liveDelay", "carState", "carControl", "carOutput"], subs=["liveTorqueParameters"], ignore=["logMonoTime"], init_callback=get_car_params_callback, @@ -573,7 +573,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState", "carState", "carControl"], + pubs=["deviceState", "roadCameraState", "wideRoadCameraState", "liveCalibration", "liveDelay", "driverMonitoringState", "carState", "carControl"], subs=["modelV2", "drivingModelData", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime", "drivingModelData.frameDropPerc", "drivingModelData.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 5f5cb89089..b091f2b689 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -5c8f6c360b66f269a24d519e699addf9e9bcaefe \ No newline at end of file +6ad91de126c2a9827b1a0536068fd79d9244eaa2 \ No newline at end of file From c24f3498079a8b5f7202ccd62dc786e0e0380bce Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 May 2025 19:43:17 -0700 Subject: [PATCH 140/142] GM: low steer speed alert in CarState (#35332) * simple * rm * bump to master * update refs * update refs --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 2 -- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 0c8b12328e..3ae4dc13cd 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 0c8b12328ec4fcb7481806f62cd90310573d4c0c +Subproject commit 3ae4dc13cd9e628672a01651896021214b7c3c17 diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index ed3a0cb9d8..d441c648ff 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -104,8 +104,6 @@ class CarSpecificEvents: events.add(EventName.belowEngageSpeed) if CS.cruiseState.standstill: events.add(EventName.resumeRequired) - if CS.vEgo < self.CP.minSteerSpeed: - events.add(EventName.belowSteerSpeed) elif self.CP.brand == 'volkswagen': events = self.create_common_events(CS, CS_prev, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index b091f2b689..e692f56eba 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -6ad91de126c2a9827b1a0536068fd79d9244eaa2 \ No newline at end of file +9e2fe2942fbf77f24bccdbef15893831f9c0b390 \ No newline at end of file From 840ced5005eefacde09976555d0226155f849cb9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 25 May 2025 23:41:03 +0800 Subject: [PATCH 141/142] system/ui: add EGL support to CameraView (#35338) * add EGL support to CameraView * view 3 cameras * use a more direct approach * add new line * cleanup * cleanup close() * extract EGL to a seperate file * cleanup * add try/except to close() * rename egl_textures * improve implementation --- system/ui/lib/egl.py | 177 ++++++++++++++++++++++++++++++++ system/ui/widgets/cameraview.py | 152 ++++++++++++++++++++++----- 2 files changed, 301 insertions(+), 28 deletions(-) create mode 100644 system/ui/lib/egl.py diff --git a/system/ui/lib/egl.py b/system/ui/lib/egl.py new file mode 100644 index 0000000000..d43be482b3 --- /dev/null +++ b/system/ui/lib/egl.py @@ -0,0 +1,177 @@ +import os +import cffi +from dataclasses import dataclass +from typing import Any +from openpilot.common.swaglog import cloudlog + + +# EGL constants +EGL_LINUX_DMA_BUF_EXT = 0x3270 +EGL_WIDTH = 0x3057 +EGL_HEIGHT = 0x3056 +EGL_LINUX_DRM_FOURCC_EXT = 0x3271 +EGL_DMA_BUF_PLANE0_FD_EXT = 0x3272 +EGL_DMA_BUF_PLANE0_OFFSET_EXT = 0x3273 +EGL_DMA_BUF_PLANE0_PITCH_EXT = 0x3274 +EGL_DMA_BUF_PLANE1_FD_EXT = 0x3275 +EGL_DMA_BUF_PLANE1_OFFSET_EXT = 0x3276 +EGL_DMA_BUF_PLANE1_PITCH_EXT = 0x3277 +EGL_NONE = 0x3038 +GL_TEXTURE0 = 0x84C0 +GL_TEXTURE_EXTERNAL_OES = 0x8D65 + +# DRM Format for NV12 +DRM_FORMAT_NV12 = 842094158 + +@dataclass +class EGLImage: + """Container for EGL image and associated resources""" + + egl_image: Any + fd: int + + +@dataclass +class EGLState: + """Container for all EGL-related state""" + + initialized: bool = False + ffi: Any = None + egl_lib: Any = None + gles_lib: Any = None + + # EGL display connection - shared across all users + display: Any = None + + # Constants + NO_CONTEXT: Any = None + NO_DISPLAY: Any = None + NO_IMAGE_KHR: Any = None + + # Function pointers + get_current_display: Any = None + create_image_khr: Any = None + destroy_image_khr: Any = None + image_target_texture: Any = None + get_error: Any = None + bind_texture: Any = None + active_texture: Any = None + + +# Create a single instance of the state +_egl = EGLState() + + +def init_egl() -> bool: + """Initialize EGL and load necessary functions""" + global _egl + + # Don't re-initialize if already done + if _egl.initialized: + return True + + try: + _egl.ffi = cffi.FFI() + _egl.ffi.cdef(""" + typedef int EGLint; + typedef unsigned int EGLBoolean; + typedef unsigned int EGLenum; + typedef unsigned int GLenum; + typedef void *EGLContext; + typedef void *EGLDisplay; + typedef void *EGLClientBuffer; + typedef void *EGLImageKHR; + typedef void *GLeglImageOES; + + EGLDisplay eglGetCurrentDisplay(void); + EGLint eglGetError(void); + EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, + EGLenum target, EGLClientBuffer buffer, + const EGLint *attrib_list); + EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image); + void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image); + void glBindTexture(GLenum target, unsigned int texture); + void glActiveTexture(GLenum texture); + """) + + # Load libraries + _egl.egl_lib = _egl.ffi.dlopen("libEGL.so") + _egl.gles_lib = _egl.ffi.dlopen("libGLESv2.so") + + # Cast NULL pointers + _egl.NO_CONTEXT = _egl.ffi.cast("void *", 0) + _egl.NO_DISPLAY = _egl.ffi.cast("void *", 0) + _egl.NO_IMAGE_KHR = _egl.ffi.cast("void *", 0) + + # Bind functions + _egl.get_current_display = _egl.egl_lib.eglGetCurrentDisplay + _egl.create_image_khr = _egl.egl_lib.eglCreateImageKHR + _egl.destroy_image_khr = _egl.egl_lib.eglDestroyImageKHR + _egl.image_target_texture = _egl.gles_lib.glEGLImageTargetTexture2DOES + _egl.get_error = _egl.egl_lib.eglGetError + _egl.bind_texture = _egl.gles_lib.glBindTexture + _egl.active_texture = _egl.gles_lib.glActiveTexture + + # Initialize EGL display once here + _egl.display = _egl.get_current_display() + if _egl.display == _egl.NO_DISPLAY: + raise RuntimeError("Failed to get EGL display") + + _egl.initialized = True + return True + except Exception as e: + cloudlog.exception(f"EGL initialization failed: {e}") + _egl.initialized = False + return False + + +def create_egl_image(width: int, height: int, stride: int, fd: int, uv_offset: int) -> EGLImage | None: + assert _egl.initialized, "EGL not initialized" + + # Duplicate fd since EGL needs it + dup_fd = os.dup(fd) + + # Create image attributes for EGL + img_attrs = [ + EGL_WIDTH, width, + EGL_HEIGHT, height, + EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_NV12, + EGL_DMA_BUF_PLANE0_FD_EXT, dup_fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, stride, + EGL_DMA_BUF_PLANE1_FD_EXT, dup_fd, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, uv_offset, + EGL_DMA_BUF_PLANE1_PITCH_EXT, stride, + EGL_NONE + ] + + attr_array = _egl.ffi.new("int[]", img_attrs) + egl_image = _egl.create_image_khr(_egl.display, _egl.NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, _egl.ffi.NULL, attr_array) + + if egl_image == _egl.NO_IMAGE_KHR: + cloudlog.error(f"Failed to create EGL image: {_egl.get_error()}") + os.close(dup_fd) + return None + + return EGLImage(egl_image=egl_image, fd=dup_fd) + + +def destroy_egl_image(egl_image: EGLImage) -> None: + assert _egl.initialized, "EGL not initialized" + + _egl.destroy_image_khr(_egl.display, egl_image.egl_image) + + # Close the duplicated fd we created in create_egl_image() + # We need to handle OSError since the fd might already be closed + try: + os.close(egl_image.fd) + except OSError: + pass + + +def bind_egl_image_to_texture(texture_id: int, egl_image: EGLImage) -> None: + assert _egl.initialized, "EGL not initialized" + + _egl.active_texture(GL_TEXTURE0) + _egl.bind_texture(GL_TEXTURE_EXTERNAL_OES, texture_id) + _egl.image_target_texture(GL_TEXTURE_EXTERNAL_OES, egl_image.egl_image) diff --git a/system/ui/widgets/cameraview.py b/system/ui/widgets/cameraview.py index 09fe306176..01aac0370b 100644 --- a/system/ui/widgets/cameraview.py +++ b/system/ui/widgets/cameraview.py @@ -1,6 +1,8 @@ import pyray as rl -from msgq.visionipc import VisionIpcClient, VisionStreamType +from openpilot.system.hardware import TICI +from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.egl import init_egl, create_egl_image, destroy_egl_image, bind_egl_image_to_texture, EGLImage VERTEX_SHADER = """ @@ -19,30 +21,67 @@ void main() { } """ -FRAME_FRAGMENT_SHADER = """ -#version 300 es -precision mediump float; -in vec2 fragTexCoord; -uniform sampler2D texture0; -uniform sampler2D texture1; -out vec4 fragColor; -void main() { - float y = texture(texture0, fragTexCoord).r; - vec2 uv = texture(texture1, fragTexCoord).ra - 0.5; - fragColor = vec4(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x, 1.0); -} -""" +# Choose fragment shader based on platform capabilities +if TICI: + FRAME_FRAGMENT_SHADER = """ + #version 300 es + #extension GL_OES_EGL_image_external_essl3 : enable + precision mediump float; + in vec2 fragTexCoord; + uniform samplerExternalOES texture0; + out vec4 fragColor; + void main() { + vec4 color = texture(texture0, fragTexCoord); + fragColor = vec4(pow(color.rgb, vec3(1.0/1.28)), color.a); + } + """ +else: + FRAME_FRAGMENT_SHADER = """ + #version 300 es + precision mediump float; + in vec2 fragTexCoord; + uniform sampler2D texture0; + uniform sampler2D texture1; + out vec4 fragColor; + void main() { + float y = texture(texture0, fragTexCoord).r; + vec2 uv = texture(texture1, fragTexCoord).ra - 0.5; + fragColor = vec4(y + 1.402*uv.y, y - 0.344*uv.x - 0.714*uv.y, y + 1.772*uv.x, 1.0); + } + """ class CameraView: def __init__(self, name: str, stream_type: VisionStreamType): self.client = VisionIpcClient(name, stream_type, False) self.shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAME_FRAGMENT_SHADER) + + self.frame: VisionBuf | None = None self.texture_y: rl.Texture | None = None self.texture_uv: rl.Texture | None = None - self.frame = None - def close(self): + # EGL resources + self.egl_images: dict[int, EGLImage] = {} + self.egl_texture: rl.Texture | None = None + + # Initialize EGL for zero-copy rendering on TICI + if TICI: + if not init_egl(): + raise RuntimeError("Failed to initialize EGL") + + # Create a 1x1 pixel placeholder texture for EGL image binding + temp_image = rl.gen_image_color(1, 1, rl.BLACK) + self.egl_texture = rl.load_texture_from_image(temp_image) + rl.unload_image(temp_image) + + def close(self) -> None: self._clear_textures() + + # Clean up EGL texture + if TICI and self.egl_texture: + rl.unload_texture(self.egl_texture) + self.egl_texture = None + + # Clean up shader if self.shader and self.shader.id: rl.unload_shader(self.shader) @@ -50,17 +89,14 @@ class CameraView: if not self._ensure_connection(): return + # Try to get a new buffer without blocking buffer = self.client.recv(timeout_ms=0) - self.frame = buffer if buffer else self.frame - if not self.frame or not self.texture_y or not self.texture_uv: + if buffer: + self.frame = buffer + + if not self.frame: return - y_data = self.frame.data[: self.frame.uv_offset] - uv_data = self.frame.data[self.frame.uv_offset :] - - rl.update_texture(self.texture_y, rl.ffi.cast("void *", y_data.ctypes.data)) - rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data)) - # Calculate scaling to maintain aspect ratio scale = min(rect.width / self.frame.width, rect.height / self.frame.height) x_offset = rect.x + (rect.width - (self.frame.width * scale)) / 2 @@ -68,6 +104,53 @@ class CameraView: src_rect = rl.Rectangle(0, 0, float(self.frame.width), float(self.frame.height)) dst_rect = rl.Rectangle(x_offset, y_offset, self.frame.width * scale, self.frame.height * scale) + # Render with appropriate method + if TICI: + self._render_egl(src_rect, dst_rect) + else: + self._render_textures(src_rect, dst_rect) + + def _render_egl(self, src_rect: rl.Rectangle, dst_rect: rl.Rectangle) -> None: + """Render using EGL for direct buffer access""" + if self.frame is None or self.egl_texture is None: + return + + idx = self.frame.idx + egl_image = self.egl_images.get(idx) + + # Create EGL image if needed + if egl_image is None: + egl_image = create_egl_image(self.frame.width, self.frame.height, self.frame.stride, self.frame.fd, self.frame.uv_offset) + if egl_image: + self.egl_images[idx] = egl_image + else: + return + + # Update texture dimensions to match current frame + self.egl_texture.width = self.frame.width + self.egl_texture.height = self.frame.height + + # Bind the EGL image to our texture + bind_egl_image_to_texture(self.egl_texture.id, egl_image) + + # Render with shader + rl.begin_shader_mode(self.shader) + rl.draw_texture_pro(self.egl_texture, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) + rl.end_shader_mode() + + def _render_textures(self, src_rect: rl.Rectangle, dst_rect: rl.Rectangle) -> None: + """Render using texture copies""" + if not self.texture_y or not self.texture_uv or self.frame is None: + return + + # Update textures with new frame data + y_data = self.frame.data[: self.frame.uv_offset] + uv_data = self.frame.data[self.frame.uv_offset :] + + rl.update_texture(self.texture_y, rl.ffi.cast("void *", y_data.ctypes.data)) + rl.update_texture(self.texture_uv, rl.ffi.cast("void *", uv_data.ctypes.data)) + + # Render with shader rl.begin_shader_mode(self.shader) rl.set_shader_value_texture(self.shader, rl.get_shader_location(self.shader, "texture1"), self.texture_uv) rl.draw_texture_pro(self.texture_y, src_rect, dst_rect, rl.Vector2(0, 0), 0.0, rl.WHITE) @@ -80,17 +163,30 @@ class CameraView: return False self._clear_textures() - self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), - int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) - self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), - int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA)) + + if not TICI: + self.texture_y = rl.load_texture_from_image(rl.Image(None, int(self.client.stride), + int(self.client.height), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE)) + self.texture_uv = rl.load_texture_from_image(rl.Image(None, int(self.client.stride // 2), + int(self.client.height // 2), 1, rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA)) + return True def _clear_textures(self): if self.texture_y and self.texture_y.id: rl.unload_texture(self.texture_y) + self.texture_y = None + if self.texture_uv and self.texture_uv.id: rl.unload_texture(self.texture_uv) + self.texture_uv = None + + # Clean up EGL resources + if TICI: + for data in self.egl_images.values(): + destroy_egl_image(data) + self.egl_images = {} + if __name__ == "__main__": gui_app.init_window("watch3") From 993b1b4d88bb55d4ab87ddd2ffc51e8cfc866522 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 25 May 2025 08:54:07 -0700 Subject: [PATCH 142/142] refactor: LPA interface, CLI location (#35328) * refactor: LPABase, simpler switch() interface * leave this since LPABase hides * hw-agnostic esim.py * newline * use latest --- system/hardware/base.py | 42 ++++++++++ system/hardware/esim.py | 38 +++++++++ system/hardware/pc/hardware.py | 6 +- system/hardware/tici/esim.py | 105 ++++++------------------ system/hardware/tici/hardware.py | 6 +- system/hardware/tici/tests/test_esim.py | 8 +- tools/op.sh | 2 +- 7 files changed, 117 insertions(+), 90 deletions(-) create mode 100755 system/hardware/esim.py mode change 100755 => 100644 system/hardware/tici/esim.py diff --git a/system/hardware/base.py b/system/hardware/base.py index 90b42b2f1f..e429f0e9f2 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -6,6 +6,19 @@ from cereal import log NetworkType = log.DeviceState.NetworkType +class LPAError(RuntimeError): + pass + +class LPAProfileNotFoundError(LPAError): + pass + +@dataclass +class Profile: + iccid: str + nickname: str + enabled: bool + provider: str + @dataclass class ThermalZone: # a zone from /sys/class/thermal/thermal_zone* @@ -51,6 +64,31 @@ class ThermalConfig: ret[f.name + "TempC"] = v.read() return ret +class LPABase(ABC): + @abstractmethod + def list_profiles(self) -> list[Profile]: + pass + + @abstractmethod + def get_active_profile(self) -> Profile | None: + pass + + @abstractmethod + def delete_profile(self, iccid: str) -> None: + pass + + @abstractmethod + def download_profile(self, qr: str, nickname: str | None = None) -> None: + pass + + @abstractmethod + def nickname_profile(self, iccid: str, nickname: str) -> None: + pass + + @abstractmethod + def switch_profile(self, iccid: str) -> None: + pass + class HardwareBase(ABC): @staticmethod def get_cmdline() -> dict[str, str]: @@ -105,6 +143,10 @@ class HardwareBase(ABC): def get_sim_info(self): pass + @abstractmethod + def get_sim_lpa(self) -> LPABase: + pass + @abstractmethod def get_network_strength(self, network_type): pass diff --git a/system/hardware/esim.py b/system/hardware/esim.py new file mode 100755 index 0000000000..6668a1cdd3 --- /dev/null +++ b/system/hardware/esim.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import argparse + +from openpilot.system.hardware import HARDWARE + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') + parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') + parser.add_argument('--switch', metavar='iccid', help='switch to profile') + parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') + parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') + parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile') + args = parser.parse_args() + + lpa = HARDWARE.get_sim_lpa() + if args.switch: + lpa.switch_profile(args.switch) + elif args.delete: + confirm = input('are you sure you want to delete this profile? (y/N) ') + if confirm == 'y': + lpa.delete_profile(args.delete) + print('deleted profile, please restart device to apply changes') + else: + print('cancelled') + exit(0) + elif args.download: + lpa.download_profile(args.download[0], args.download[1]) + elif args.nickname: + lpa.nickname_profile(args.nickname[0], args.nickname[1]) + else: + parser.print_help() + + profiles = lpa.list_profiles() + print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') + for p in profiles: + print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 017a449c90..9a80f10bed 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,12 +1,11 @@ import random from cereal import log -from openpilot.system.hardware.base import HardwareBase +from openpilot.system.hardware.base import HardwareBase, LPABase NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength - class Pc(HardwareBase): def get_os_version(self): return None @@ -41,6 +40,9 @@ class Pc(HardwareBase): 'data_connected': False } + def get_sim_lpa(self) -> LPABase: + raise NotImplementedError("SIM LPA not implemented for PC") + def get_network_strength(self, network_type): return NetworkStrength.unknown diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py old mode 100755 new mode 100644 index 0a07e77e16..f657966ddf --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -1,28 +1,12 @@ -#!/usr/bin/env python3 - -import argparse import json import os import shutil import subprocess -from dataclasses import dataclass from typing import Literal -@dataclass -class Profile: - iccid: str - nickname: str - enabled: bool - provider: str +from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile -class LPAError(RuntimeError): - pass - -class LPAProfileNotFoundError(LPAError): - pass - - -class LPA: +class TiciLPA(LPABase): def __init__(self, interface: Literal['qmi', 'at'] = 'qmi'): self.env = os.environ.copy() self.env['LPAC_APDU'] = interface @@ -47,31 +31,13 @@ class LPA: def get_active_profile(self) -> Profile | None: return next((p for p in self.list_profiles() if p.enabled), None) - def enable_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest: - if latest.iccid == iccid: - return - self.disable_profile(latest.iccid) - self._validate_successful(self._invoke('profile', 'enable', iccid)) - self.process_notifications() - - def disable_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest is not None and latest.iccid != iccid: - return - self._validate_successful(self._invoke('profile', 'disable', iccid)) - self.process_notifications() - def delete_profile(self, iccid: str) -> None: self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest is not None and latest.iccid == iccid: - self.disable_profile(iccid) + raise LPAError('cannot delete active profile, switch to another profile first') self._validate_successful(self._invoke('profile', 'delete', iccid)) - self.process_notifications() + self._process_notifications() def download_profile(self, qr: str, nickname: str | None = None) -> None: msgs = self._invoke('profile', 'download', '-a', qr) @@ -81,17 +47,24 @@ class LPA: raise LPAError('no new profile found') if nickname: self.nickname_profile(new_profile['payload']['data']['iccid'], nickname) - self.process_notifications() + self._process_notifications() def nickname_profile(self, iccid: str, nickname: str) -> None: self._validate_profile_exists(iccid) self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) - def process_notifications(self) -> None: - """ - Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier. - """ - self._validate_successful(self._invoke('notification', 'process', '-a', '-r')) + def switch_profile(self, iccid: str) -> None: + self._enable_profile(iccid) + + def _enable_profile(self, iccid: str) -> None: + self._validate_profile_exists(iccid) + latest = self.get_active_profile() + if latest: + if latest.iccid == iccid: + return + self._validate_successful(self._invoke('profile', 'disable', latest.iccid)) + self._validate_successful(self._invoke('profile', 'enable', iccid)) + self._process_notifications() def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) @@ -124,47 +97,15 @@ class LPA: return messages + def _process_notifications(self) -> None: + """ + Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier. + """ + self._validate_successful(self._invoke('notification', 'process', '-a', '-r')) + def _validate_profile_exists(self, iccid: str) -> None: if not any(p.iccid == iccid for p in self.list_profiles()): raise LPAProfileNotFoundError(f'profile {iccid} does not exist') def _validate_successful(self, msgs: list[dict]) -> None: assert msgs[-1]['payload']['message'] == 'success', 'expected success notification' - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') - parser.add_argument('--enable', metavar='iccid', help='enable profile; will disable current profile') - parser.add_argument('--disable', metavar='iccid', help='disable profile') - parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') - parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') - parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile') - args = parser.parse_args() - - lpa = LPA(interface=args.backend) - if args.enable: - lpa.enable_profile(args.enable) - print('enabled profile, please restart device to apply changes') - elif args.disable: - lpa.disable_profile(args.disable) - print('disabled profile, please restart device to apply changes') - elif args.delete: - confirm = input('are you sure you want to delete this profile? (y/N) ') - if confirm == 'y': - lpa.delete_profile(args.delete) - print('deleted profile, please restart device to apply changes') - else: - print('cancelled') - exit(0) - elif args.download: - lpa.download_profile(args.download[0], args.download[1]) - elif args.nickname: - lpa.nickname_profile(args.nickname[0], args.nickname[1]) - else: - parser.print_help() - - profiles = lpa.list_profiles() - print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') - for p in profiles: - print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 5a8e41b51f..3add52d21c 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -10,8 +10,9 @@ from pathlib import Path from cereal import log from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action -from openpilot.system.hardware.base import HardwareBase, ThermalConfig, ThermalZone +from openpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone from openpilot.system.hardware.tici import iwlist +from openpilot.system.hardware.tici.esim import TiciLPA from openpilot.system.hardware.tici.pins import GPIO from openpilot.system.hardware.tici.amplifier import Amplifier @@ -198,6 +199,9 @@ class Tici(HardwareBase): 'data_connected': modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) == MM_MODEM_STATE.CONNECTED, } + def get_sim_lpa(self) -> LPABase: + return TiciLPA() + def get_imei(self, slot): if slot != 0: return "" diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py index d36bdaa27b..6fab931cce 100644 --- a/system/hardware/tici/tests/test_esim.py +++ b/system/hardware/tici/tests/test_esim.py @@ -1,7 +1,7 @@ import pytest -from openpilot.system.hardware import TICI -from openpilot.system.hardware.tici.esim import LPA, LPAProfileNotFoundError +from openpilot.system.hardware import HARDWARE, TICI +from openpilot.system.hardware.base import LPAProfileNotFoundError # https://euicc-manual.osmocom.org/docs/rsp/known-test-profile # iccid is always the same for the given activation code @@ -11,7 +11,7 @@ TEST_ICCID = '8944476500001944011' TEST_NICKNAME = 'test_profile' def cleanup(): - lpa = LPA() + lpa = HARDWARE.get_sim_lpa() try: lpa.delete_profile(TEST_ICCID) except LPAProfileNotFoundError: @@ -31,7 +31,7 @@ class TestEsim: cleanup() def test_provision_enable_disable(self): - lpa = LPA() + lpa = HARDWARE.get_sim_lpa() current_active = lpa.get_active_profile() lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME) diff --git a/tools/op.sh b/tools/op.sh index 7c17c949e4..9142d50a18 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -295,7 +295,7 @@ function op_check() { function op_esim() { op_before_cmd - op_run_command system/hardware/tici/esim.py "$@" + op_run_command system/hardware/esim.py "$@" } function op_build() {