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
diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml
index 95f8320d62..8b0db6ce88 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: |
diff --git a/.gitmodules b/.gitmodules
index b9f0336a0e..af2dea6495 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -15,7 +15,7 @@
url = https://github.com/commaai/teleoprtc
[submodule "tinygrad"]
path = tinygrad_repo
- url = https://github.com/commaai/tinygrad.git
+ url = https://github.com/tinygrad/tinygrad.git
[submodule "sunnypilot/neural_network_data"]
path = sunnypilot/neural_network_data
url = https://github.com/sunnypilot/neural-network-data.git
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/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)
========================
diff --git a/SConstruct b/SConstruct
index 7a2bdc5b94..f62c5b13af 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')
@@ -309,12 +304,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/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/cereal/log.capnp b/cereal/log.capnp
index 9eb503adfa..79f11f6bcf 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;
}
@@ -489,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/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():
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/common/model.h b/common/model.h
index ac6154fdf9..ac01b27691 100644
--- a/common/model.h
+++ b/common/model.h
@@ -1 +1 @@
-#define DEFAULT_MODEL "Tomb Raider 7 (Default)"
+#define DEFAULT_MODEL "Vegan Filet O Fish (Default)"
diff --git a/common/params.cc b/common/params.cc
index 94fbfa4a71..0a8821c37a 100644
--- a/common/params.cc
+++ b/common/params.cc
@@ -142,7 +142,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");
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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|None||
-|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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)|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|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 HereTools
- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep) ||
-|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|None|||
+|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,16](#footnotes)|0 mph|0 mph|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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)|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Legacy 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|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 HereTools
- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep) |||
+|Subaru|Outback 2020-22|All[8](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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 HereTools
- 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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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|[](##)|[](##)|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/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/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/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:
+
+
+* 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/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/opendbc_repo b/opendbc_repo
index c5d42d5493..c5b96b4b4c 160000
--- a/opendbc_repo
+++ b/opendbc_repo
@@ -1 +1 @@
-Subproject commit c5d42d54934a55808bea1a8acb081cfc62a273bc
+Subproject commit c5b96b4b4c357b909330426a6c516afa16b1a08c
diff --git a/panda b/panda
index 77869d76e8..5a1b1c9225 160000
--- a/panda
+++ b/panda
@@ -1 +1 @@
-Subproject commit 77869d76e84f381662ea893498e75e224298f7f3
+Subproject commit 5a1b1c9225206e9a4d11e06790f5a571de7fa674
diff --git a/pyproject.toml b/pyproject.toml
index f0c3f7628a..3287bc259a 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"]
@@ -170,9 +170,9 @@ 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/*"
+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"
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/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/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/backspace.png b/selfdrive/assets/icons/backspace.png
new file mode 100644
index 0000000000..16686be6f5
--- /dev/null
+++ b/selfdrive/assets/icons/backspace.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:576da562df8eb513e64ccb614ec727b257cbca8b5507974d01efc0f64c5382c2
+size 6267
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/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/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/checkmark.svg b/selfdrive/assets/icons/checkmark.svg
new file mode 100644
index 0000000000..26480698cd
--- /dev/null
+++ b/selfdrive/assets/icons/checkmark.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:17a135c18634647a73734300f5d0ad98082b07779b5553e72b99686857380ee7
+size 244
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/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_check.svg b/selfdrive/assets/icons/circled_check.svg
new file mode 100644
index 0000000000..aab06ec1e0
--- /dev/null
+++ b/selfdrive/assets/icons/circled_check.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5c88458f6326265965626cbc97c2219bd513b15a6468b08f80b43ef014b7904b
+size 372
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/circled_slash.svg b/selfdrive/assets/icons/circled_slash.svg
new file mode 100644
index 0000000000..89c6e2b1ae
--- /dev/null
+++ b/selfdrive/assets/icons/circled_slash.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d91c86028af58cbe2770799d0fe7e55d143f9b3f67fdebcb47d328d6e410285
+size 223
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/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.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/close2.svg b/selfdrive/assets/icons/close2.svg
new file mode 100644
index 0000000000..56a36cecd5
--- /dev/null
+++ b/selfdrive/assets/icons/close2.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7d2a7cd3913ab97a386e946969f6a684895adf5d641c38dfd8f5efa0197a6c58
+size 513
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/couch.svg b/selfdrive/assets/icons/couch.svg
new file mode 100644
index 0000000000..f56970f289
--- /dev/null
+++ b/selfdrive/assets/icons/couch.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dd5d8d3fce5e30662797f7eeed224f5775b2cabcc055f01f5ebf6e7b2657b616
+size 1801
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/disengage_on_accelerator.svg b/selfdrive/assets/icons/disengage_on_accelerator.svg
new file mode 100644
index 0000000000..eef5181935
--- /dev/null
+++ b/selfdrive/assets/icons/disengage_on_accelerator.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:baac01efc894527c8234b774c89cc57b69460d60ad81691a6d50ce0904c60ba7
+size 3638
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/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.svg b/selfdrive/assets/icons/experimental.svg
new file mode 100644
index 0000000000..8a97cdeac1
--- /dev/null
+++ b/selfdrive/assets/icons/experimental.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b7ab989c9fe22e7d2119bac5284cdccfba2889477d69f5f6c6b4470c7ee0aab3
+size 1801
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_grey.svg b/selfdrive/assets/icons/experimental_grey.svg
new file mode 100644
index 0000000000..25ab33f388
--- /dev/null
+++ b/selfdrive/assets/icons/experimental_grey.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:90d441310f8e1833c661d8a4f0546aab2eb50eb21cc21beb0aff0d27b5ee6066
+size 1571
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/experimental_white.svg b/selfdrive/assets/icons/experimental_white.svg
new file mode 100644
index 0000000000..51d7698947
--- /dev/null
+++ b/selfdrive/assets/icons/experimental_white.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:82648254da89edb0cf65ef63fc5e6e0741414051bfea8a40449132fd61b1ed0f
+size 1533
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_closed.svg b/selfdrive/assets/icons/eye_closed.svg
new file mode 100644
index 0000000000..a9cb925186
--- /dev/null
+++ b/selfdrive/assets/icons/eye_closed.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fcb0db3b77d6057b94544b6d91b6078fbd91b8f2de189322b90feae3d1ac29da
+size 1054
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/eye_open.svg b/selfdrive/assets/icons/eye_open.svg
new file mode 100644
index 0000000000..2befa13adb
--- /dev/null
+++ b/selfdrive/assets/icons/eye_open.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:411774f4a80831833a344e58932c23a6ebd1db73a6327a63259deca2c6f53613
+size 597
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/lock_closed.svg b/selfdrive/assets/icons/lock_closed.svg
new file mode 100644
index 0000000000..22a510d1c2
--- /dev/null
+++ b/selfdrive/assets/icons/lock_closed.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:64e8fc90b79c725a8bf5bbc99643989ae885570997092762e216738805649ade
+size 492
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/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/icons/shift.png b/selfdrive/assets/icons/shift.png
new file mode 100644
index 0000000000..de2a68b482
--- /dev/null
+++ b/selfdrive/assets/icons/shift.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a93dd816bd0600ad47d10ebe530326bfa725dc31e4d5e1ee275f39b10f17a59d
+size 4931
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/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/triangle.svg b/selfdrive/assets/icons/triangle.svg
new file mode 100644
index 0000000000..233eb5e979
--- /dev/null
+++ b/selfdrive/assets/icons/triangle.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a83c9a78673429caf16d02917be4c8afedadff17b6ceb8c248703ad1120116ed
+size 394
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/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_full.svg b/selfdrive/assets/icons/wifi_strength_full.svg
new file mode 100644
index 0000000000..4137d3cd4c
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_full.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3c16c005d666dea64ba6f85de50dfcf161353176eca3d9f475677cbc04fd0382
+size 1161
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_high.svg b/selfdrive/assets/icons/wifi_strength_high.svg
new file mode 100644
index 0000000000..17d721fe31
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_high.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0f207b9a1f4ab6009e261f03edd1d05a9d485217415664cba0253d32381e3a03
+size 1164
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_low.svg b/selfdrive/assets/icons/wifi_strength_low.svg
new file mode 100644
index 0000000000..4088866370
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_low.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6835937483e5a539618a94e3ec8fad661ae2f7afb39c958e6c8b007b8f4b9b2c
+size 1198
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_strength_medium.svg b/selfdrive/assets/icons/wifi_strength_medium.svg
new file mode 100644
index 0000000000..f0c029dedd
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_strength_medium.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7ebb1fb5539d9f8ea9899acf4f9cb359503dbe22cd38a3461ad43936348475b9
+size 1167
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/icons/wifi_uploading.svg b/selfdrive/assets/icons/wifi_uploading.svg
new file mode 100644
index 0000000000..07a14a59f4
--- /dev/null
+++ b/selfdrive/assets/icons/wifi_uploading.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e7cfefbda22b53f7eb72a25893ab41c00dabea49f690cafb1d224375a669e608
+size 1170
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/images/button_continue_triangle.svg b/selfdrive/assets/images/button_continue_triangle.svg
new file mode 100644
index 0000000000..e6d362927c
--- /dev/null
+++ b/selfdrive/assets/images/button_continue_triangle.svg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8084e5a8bbc16956a98b010092ae1c4e32b3391b0551fa1cd65cb1e2bb59d3df
+size 169
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/assets/images/triangle.svg b/selfdrive/assets/images/triangle.svg
deleted file mode 100644
index f3a8db44b7..0000000000
--- a/selfdrive/assets/images/triangle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:aaf9a1967365f2c641bf7b54a409e32842cee0bfded7ebb01b1a95c4ac9f0154
-size 2163
diff --git a/selfdrive/assets/img_circled_check.svg b/selfdrive/assets/img_circled_check.svg
deleted file mode 100644
index 1852ba947a..0000000000
--- a/selfdrive/assets/img_circled_check.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c5687faf4cb22bece0405893671651eb13e8eda393987610f493ee5e05eac61d
-size 439
diff --git a/selfdrive/assets/img_circled_slash.svg b/selfdrive/assets/img_circled_slash.svg
deleted file mode 100644
index 6c77030d15..0000000000
--- a/selfdrive/assets/img_circled_slash.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fbec38447732a443c304042c3c3d362c94e11e794e7cc7dc86aa1c7bba16c6b6
-size 328
diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/img_continue_triangle.svg
deleted file mode 100644
index ee9db369a4..0000000000
--- a/selfdrive/assets/img_continue_triangle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:1764c0c93703481b2ced63bc35c5a23a6c12388ca690ce4cf577b3b478a08b69
-size 197
diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg
deleted file mode 100644
index 7c58515200..0000000000
--- a/selfdrive/assets/img_couch.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a428d2561198ebdc853ba6fb25a8b9c5a58064d000413694e1535adc06633c0a
-size 2649
diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg
deleted file mode 100644
index 3c31caa07e..0000000000
--- a/selfdrive/assets/img_experimental.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c26afadff128244567a7cf98f1c998f97056b19209301ff7fc8851eb807fb748
-size 2193
diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg
deleted file mode 100644
index 8ba6c87bd5..0000000000
--- a/selfdrive/assets/img_experimental_grey.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5ae28a53171567c8a0d52eec75cc49004cb8dd19dcab9a360784718ab8ec7c02
-size 1931
diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg
deleted file mode 100644
index 9714fe0c01..0000000000
--- a/selfdrive/assets/img_experimental_white.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:99be696be983700d7eb1768bd1c840198a4eb9525b71e28efea49f13c358e519
-size 1891
diff --git a/selfdrive/assets/img_eye_closed.svg b/selfdrive/assets/img_eye_closed.svg
deleted file mode 100644
index fcef6e8a3c..0000000000
--- a/selfdrive/assets/img_eye_closed.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:8e0e5b451ed2e426fea99da24a9df6552be7048205b9aa7b89e46eeb89f6d08e
-size 1490
diff --git a/selfdrive/assets/img_eye_open.svg b/selfdrive/assets/img_eye_open.svg
deleted file mode 100644
index 7289c4a571..0000000000
--- a/selfdrive/assets/img_eye_open.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:23935f9e2ddba8dafd4a5d0217f29783260a0832c9b0d3e6a2ef66d4529b91d2
-size 775
diff --git a/selfdrive/assets/offroad/icon_checkmark.svg b/selfdrive/assets/offroad/icon_checkmark.svg
deleted file mode 100644
index 7a1db13497..0000000000
--- a/selfdrive/assets/offroad/icon_checkmark.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:da1540859c4c42878a32a0a81a38ca38b04be4cb0fe46709df3e024b7f3b4036
-size 243
diff --git a/selfdrive/assets/offroad/icon_close.svg b/selfdrive/assets/offroad/icon_close.svg
deleted file mode 100644
index 54a44146d7..0000000000
--- a/selfdrive/assets/offroad/icon_close.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:4eff44a05132ed9f99ad821993be4bab9b1a1c880e07441f3b887214bee62afe
-size 825
diff --git a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
deleted file mode 100644
index d5a8c87e21..0000000000
--- a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:5252412f2225c89ea7e78ad0fbd6544170aea157693ce0f9778f26a64f582ec2
-size 6821
diff --git a/selfdrive/assets/offroad/icon_lock_closed.svg b/selfdrive/assets/offroad/icon_lock_closed.svg
deleted file mode 100644
index 501d978a8a..0000000000
--- a/selfdrive/assets/offroad/icon_lock_closed.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:ef09792cc1893f81c64abd6b72091d3762c07b849863ce90508afdd29b392c02
-size 732
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_full.svg b/selfdrive/assets/offroad/icon_wifi_strength_full.svg
deleted file mode 100644
index c9d22d8961..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_full.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:485dd0d4eb8968726003ae460bab4ff498127def52b8b1ed6d968f4629ab233a
-size 1655
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_high.svg b/selfdrive/assets/offroad/icon_wifi_strength_high.svg
deleted file mode 100644
index ff001bd14a..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_high.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:c92fd8bebbe630f991fe3f61f61407f8002d29881698634239be3a6fae2fb4bc
-size 1657
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_low.svg b/selfdrive/assets/offroad/icon_wifi_strength_low.svg
deleted file mode 100644
index bcc6e83c63..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_low.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e629e3a84dc288278e455be4a8deff95730e24d9a0b894f8c417ac1c409670ab
-size 1661
diff --git a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg
deleted file mode 100644
index e14ebd238f..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:52113c14656483854a814c50c93770f5e6ab7d63b2b5048ddd6fd47fdfd4a7de
-size 1659
diff --git a/selfdrive/assets/offroad/icon_wifi_uploading.svg b/selfdrive/assets/offroad/icon_wifi_uploading.svg
deleted file mode 100644
index b9392dfe7c..0000000000
--- a/selfdrive/assets/offroad/icon_wifi_uploading.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:bedd579f56c65fffe2ad571f92843b6d103826fe173f8f5b58fc2200d2ad8850
-size 1663
diff --git a/selfdrive/assets/prep-svg.sh b/selfdrive/assets/prep-svg.sh
new file mode 100755
index 0000000000..2332cd25c5
--- /dev/null
+++ b/selfdrive/assets/prep-svg.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+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-right
+ backspace
+ capslock-fill
+ shift
+ shift-fill
+)
+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
+
+for svg in $(find $DIR -type f | grep svg$); do
+ bunx svgo $svg --multipass --pretty --indent 2
+
+ # convert to 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
+
+# cleanup bootstrap SVGs
+for id in "${ICON_IDS[@]}"; do
+ rm "${ICONS_DIR}/${id}.svg"
+done
diff --git a/selfdrive/assets/strip-svg-metadata.sh b/selfdrive/assets/strip-svg-metadata.sh
deleted file mode 100755
index e51dc9481d..0000000000
--- a/selfdrive/assets/strip-svg-metadata.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-# 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
-done
diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py
index dc37519be1..d441c648ff 100644
--- a/selfdrive/car/car_specific.py
+++ b/selfdrive/car/car_specific.py
@@ -1,11 +1,7 @@
-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,14 +35,12 @@ 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()
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])
@@ -110,21 +104,11 @@ 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],
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)
@@ -136,20 +120,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)
-
- # 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)
+ pcm_enable=self.CP.pcmCruise, allow_button_cancel=False)
else:
events = self.create_common_events(CS, CS_prev)
@@ -157,7 +129,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 +208,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/car/card.py b/selfdrive/car/card.py
index 64e180367b..ed9a2d1cd3 100755
--- a/selfdrive/car/card.py
+++ b/selfdrive/car/card.py
@@ -18,7 +18,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
@@ -119,12 +118,7 @@ class Car:
self.CI, self.CP, self.CP_SP = CI, CI.CP, CI.CP_SP
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
-
# mads
set_alternative_experience(self.CP, self.params)
set_car_specific_params(self.CP, self.CP_SP, self.params)
@@ -133,9 +127,7 @@ class Car:
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
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/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py
index 7bd0abc115..fe6275d9d0 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, CP_SP, CI):
super().__init__(CP, CP_SP, 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' 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)
+ # 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
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/locationd/lagd.py b/selfdrive/locationd/lagd.py
index f4f46b7469..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,18 +311,31 @@ 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_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 +350,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 +363,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 +388,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)
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
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
diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript
index 0c04ec2823..10f3876190 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']
@@ -38,19 +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 + '"'
-if arch == 'larch64':
- device_string = 'QCOM=1'
-elif arch == 'Darwin':
- device_string = 'CLANG=1 IMAGE=0'
+def tg_compile(flags, model_name):
+ pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
+ fn = File(f"models/{model_name}").abspath
+ 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:
- device_string = 'LLVM=1 LLVMOPT=1 BEAM=0 IMAGE=0'
-
-# TODO-SP: after 15.4 it's not possible to compile models locally on mac https://discord.com/channels/469524606043160576/1362735424644055230
-if arch != 'Darwin':
- for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
- 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)
-
+ print("USB GPU not detected... skipping")
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/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..1e3d1782e6 100755
--- a/selfdrive/modeld/modeld.py
+++ b/selfdrive/modeld/modeld.py
@@ -1,13 +1,18 @@
#!/usr/bin/env python3
import os
from openpilot.system.hardware import TICI
-from tinygrad.tensor import Tensor
-from tinygrad.dtype import dtypes
-if 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'
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
@@ -26,7 +31,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 +46,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 +60,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:
@@ -150,7 +151,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:
@@ -176,7 +177,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:
@@ -191,13 +192,17 @@ 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)
+ 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/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx
index 5bd77bb958..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:19e30484236efff72d519938c3e26461dbeb89c11d81fa7ecbff8e0263333c18
-size 15588463
+oid sha256:abbb1f5a74a6d047ed4b7f376b19451a9443986660f136afd0f8d76fc254a579
+size 15976894
diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx
index 0a7b4a803d..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:dad289ae367cefcb862ef1d707fb4919d008f0eeaa1ebaf18df58d8de5a7d96e
-size 46265585
+oid sha256:6dfffac033d7ae8e68aa5763bd9b5dd99ddc748f6a95523c84fc2523eefdec55
+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/pandad/pandad.cc b/selfdrive/pandad/pandad.cc
index 1b4defd2b9..af27276510 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/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
diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py
index 16f287fde9..695111bd58 100755
--- a/selfdrive/selfdrived/events.py
+++ b/selfdrive/selfdrived/events.py
@@ -727,7 +727,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"),
},
@@ -815,6 +815,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 2ebc2815bb..e32d7dfb23 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, custom
from msgq.visionipc import VisionIpcClient, VisionStreamType
-from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
@@ -67,7 +66,6 @@ class SelfdriveD(CruiseHelper):
self.CP_SP = CP_SP
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'] + ['selfdriveStateSP', 'onroadEventsSP'])
@@ -89,7 +87,7 @@ class SelfdriveD(CruiseHelper):
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))
@@ -97,6 +95,7 @@ class SelfdriveD(CruiseHelper):
# 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'
@@ -181,7 +180,11 @@ class SelfdriveD(CruiseHelper):
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
@@ -541,6 +544,7 @@ class SelfdriveD(CruiseHelper):
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()
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
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/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/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index d73faf996e..be7411ef70 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
@@ -367,9 +366,6 @@ def get_car_params_callback(rc, pm, msgs, fingerprint):
_CI = get_car(*can_callbacks, lambda obd: None, Params().get_bool("AlphaLongitudinalEnabled"), cached_params=cached_params)
CP, CP_SP = _CI.CP, _CI.CP_SP
- if not params.get_bool("DisengageOnAccelerator"):
- CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
-
params.put("CarParams", CP.to_bytes())
params.put("CarParamsSP", convert_to_capnp(CP_SP).to_bytes())
@@ -772,9 +768,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 3dd3eab25f..e692f56eba 100644
--- a/selfdrive/test/process_replay/ref_commit
+++ b/selfdrive/test/process_replay/ref_commit
@@ -1 +1 @@
-7bf4ae5b92a3ad1f073f675e24e28babad0f2aa0
\ No newline at end of file
+9e2fe2942fbf77f24bccdbef15893831f9c0b390
\ 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 eb06489ad0..14e1b2b79f 100755
--- a/selfdrive/test/process_replay/test_processes.py
+++ b/selfdrive/test/process_replay/test_processes.py
@@ -37,6 +37,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
@@ -60,6 +61,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
@@ -251,7 +253,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")
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
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/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc
index 02c137413c..7ace68ef94 100644
--- a/selfdrive/ui/qt/network/networking.cc
+++ b/selfdrive/ui/qt/network/networking.cc
@@ -179,14 +179,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"));
@@ -217,18 +239,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
@@ -240,12 +276,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/network/networking.h b/selfdrive/ui/qt/network/networking.h
index a5bb83f8ac..73c2226a34 100644
--- a/selfdrive/ui/qt/network/networking.h
+++ b/selfdrive/ui/qt/network/networking.h
@@ -11,6 +11,7 @@
#ifdef SUNNYPILOT
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
#define ButtonControl ButtonControlSP
+#define MultiButtonControl MultiButtonControlSP
#define ElidedLabel ElidedLabelSP
#define LabelControl LabelControlSP
#define ListWidget ListWidgetSP
@@ -76,7 +77,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 6fa7700cdd..8a104c0fbe 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/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc
index 457b4053a7..a3288a3e96 100644
--- a/selfdrive/ui/qt/offroad/experimental_mode.cc
+++ b/selfdrive/ui/qt/offroad/experimental_mode.cc
@@ -15,8 +15,8 @@ constexpr int toggles_settings_index = 2;
#endif
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(toggles_settings_index, "ExperimentalMode"); });
diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc
index ccfc3940db..9fc3a2edf1 100644
--- a/selfdrive/ui/qt/offroad/settings.cc
+++ b/selfdrive/ui/qt/offroad/settings.cc
@@ -23,13 +23,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"OpenpilotEnabledToggle",
tr("Enable sunnypilot"),
tr("Use the sunnypilot 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",
},
{
"DynamicExperimentalControl",
@@ -41,31 +41,31 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"DisengageOnAccelerator",
tr("Disengage on Accelerator Pedal"),
tr("When enabled, pressing the accelerator pedal will disengage sunnypilot."),
- "../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 sunnypilot 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",
},
};
@@ -75,7 +75,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
tr("Standard is recommended. In aggressive mode, sunnypilot will follow lead cars closer and be more aggressive with the gas and brake. "
"In relaxed mode sunnypilot 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
@@ -98,7 +98,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
// Toggles with confirmation dialogs
#ifndef SUNNYPILOT
- toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg");
+ toggles["ExperimentalMode"]->setActiveIcon("../assets/icons/experimental.svg");
#endif
toggles["ExperimentalMode"]->setConfirmation(true, true);
}
@@ -208,6 +208,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);
@@ -341,7 +344,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++) {
bool panel_trimmed = false;
@@ -357,7 +360,7 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
emit expandToggleDescription(param);
}
}
-
+
panel_widget->setCurrentIndex(index);
nav_btns->buttons()[index]->setChecked(true);
}
diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc
index 94f042c406..75b8a8b7e6 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/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/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc
index a9d447fc1b..382a15db91 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/controls.h b/selfdrive/ui/qt/widgets/controls.h
index 9562c4ad22..bf56ab185d 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 {
@@ -204,28 +204,27 @@ public:
QPushButton:checked:enabled {
background-color: #33Ab4C;
}
+ QPushButton:checked:disabled {
+ background-color: #9933Ab4C;
+ }
QPushButton:disabled {
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->setChecked(i == 0);
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 +237,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 +274,6 @@ public:
private:
std::string key;
Params params;
- QButtonGroup *button_group;
};
class ListWidget : public QWidget {
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/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
index 2469bfa593..72e7a5c8cb 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/lateral/mads_settings.cc
@@ -30,7 +30,7 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) {
// Steering Mode On Brake
madsSteeringMode = new ButtonParamControl("MadsSteeringMode", tr("Steering Mode on Brake Pedal"), "", "", madsSteeringModeTexts(), 500);
- QObject::connect(madsSteeringMode, &ButtonParamControl::buttonToggled, [=] {
+ QObject::connect(madsSteeringMode, &ButtonParamControl::buttonClicked, [=] {
updateToggles(offroad);
});
list->addItem(madsSteeringMode);
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
index 9dd078e52d..48a395dad7 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/settings.cc
@@ -75,16 +75,16 @@ SettingsWindowSP::SettingsWindowSP(QWidget *parent) : SettingsWindow(parent) {
QList panels = {
PanelInfo(" " + tr("Device"), device, "../../sunnypilot/selfdrive/assets/offroad/icon_home.svg"),
- PanelInfo(" " + tr("Network"), networking, "../assets/offroad/icon_network.png"),
- PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/offroad/icon_wifi_strength_full.svg"),
+ PanelInfo(" " + tr("Network"), networking, "../assets/icons/network.png"),
+ PanelInfo(" " + tr("sunnylink"), new SunnylinkPanel(this), "../assets/icons/wifi_strength_full.svg"),
PanelInfo(" " + tr("Toggles"), toggles, "../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"),
PanelInfo(" " + tr("Software"), new SoftwarePanelSP(this), "../../sunnypilot/selfdrive/assets/offroad/icon_software.png"),
PanelInfo(" " + tr("Steering"), new LateralPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"),
- PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/offroad/icon_speed_limit.png"),
+ PanelInfo(" " + tr("Cruise"), new LongitudinalPanel(this), "../assets/icons/speed_limit.png"),
PanelInfo(" " + tr("Trips"), new TripsPanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"),
PanelInfo(" " + tr("Vehicle"), new VehiclePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"),
PanelInfo(" " + tr("Firehose"), new FirehosePanel(this), "../../sunnypilot/selfdrive/assets/offroad/icon_firehose.svg"),
- PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/offroad/icon_shell.png"),
+ PanelInfo(" " + tr("Developer"), new DeveloperPanel(this), "../assets/icons/shell.png"),
};
nav_btns = new QButtonGroup(this);
diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc
index 5f0010ac33..01ea64f0cb 100644
--- a/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc
+++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/hyundai_settings.cc
@@ -17,7 +17,7 @@ HyundaiSettings::HyundaiSettings(QWidget *parent) : BrandSettingsInterface(paren
tuning_texts,
500
);
- QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonToggled, this, &HyundaiSettings::updateSettings);
+ QObject::connect(longitudinalTuningToggle, &ButtonParamControlSP::buttonClicked, this, &HyundaiSettings::updateSettings);
list->addItem(longitudinalTuningToggle);
longitudinalTuningToggle->showDescription();
}
diff --git a/selfdrive/ui/sunnypilot/qt/widgets/controls.h b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
index b5411f3bf1..e31c4047e9 100644
--- a/selfdrive/ui/sunnypilot/qt/widgets/controls.h
+++ b/selfdrive/ui/sunnypilot/qt/widgets/controls.h
@@ -214,12 +214,12 @@ private:
bool store_confirm = false;
};
-class ButtonParamControlSP : public AbstractControlSP_SELECTOR {
+class MultiButtonControlSP : public AbstractControlSP_SELECTOR {
Q_OBJECT
public:
- ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
- const std::vector &button_texts, const int minimum_button_width = 300, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
+ MultiButtonControlSP(const QString &title, const QString &desc, const QString &icon,
+ const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : AbstractControlSP_SELECTOR(title, desc, icon), button_texts(button_texts), is_inline_layout(inline_layout) {
const QString style = R"(
QPushButton {
border-radius: 20px;
@@ -243,8 +243,6 @@ public:
color: #33FFFFFF;
}
)";
- key = param.toStdString();
- int value = atoi(params.get(key).c_str());
if (inline_layout) {
button_param_layout->setMargin(0);
@@ -264,7 +262,6 @@ public:
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);
if (i == 0) button_param_layout->addSpacing(2);
@@ -280,10 +277,7 @@ public:
hlayout->addWidget(container);
}
- QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), [=](int id) {
- params.put(key, std::to_string(id));
- emit buttonToggled(id);
- });
+ QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonClicked), this, &MultiButtonControlSP::buttonClicked);
}
void setEnabled(bool enable) {
@@ -333,6 +327,7 @@ public:
}
protected:
+ QButtonGroup *button_group;
void paintEvent(QPaintEvent *event) override {
if (is_inline_layout) {
return;
@@ -363,12 +358,11 @@ protected:
}
signals:
- void buttonToggled(int btn_id);
+ void buttonClicked(int id);
private:
std::string key;
Params params;
- QButtonGroup *button_group;
std::vector button_texts;
bool button_group_enabled = true;
@@ -376,6 +370,38 @@ private:
QHBoxLayout *button_param_layout = is_inline_layout ? new QHBoxLayout() : hlayout;
};
+class ButtonParamControlSP : public MultiButtonControlSP {
+ Q_OBJECT
+public:
+ ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
+ const std::vector &button_texts, const int minimum_button_width = 225, const bool inline_layout = false) : MultiButtonControlSP(title, desc, icon,
+ button_texts, minimum_button_width, inline_layout) {
+ 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(&MultiButtonControlSP::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);
+ }
+
+ void showEvent(QShowEvent *event) override {
+ refresh();
+ }
+
+private:
+ std::string key;
+ Params params;
+};
+
class ListWidgetSP : public QWidget {
Q_OBJECT
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()}
diff --git a/sunnypilot/mads/mads.py b/sunnypilot/mads/mads.py
index 3b06290569..f16b688358 100644
--- a/sunnypilot/mads/mads.py
+++ b/sunnypilot/mads/mads.py
@@ -9,7 +9,7 @@ from cereal import log, custom
from opendbc.car import structs
from opendbc.car.hyundai.values import HyundaiFlags
-from opendbc.safety import ALTERNATIVE_EXPERIENCE
+from openpilot.common.params import Params
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param, get_mads_limited_brands
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
@@ -39,8 +39,7 @@ class ModularAssistiveDrivingSystem:
self.state_machine = StateMachine(self)
self.events = self.selfdrive.events
self.events_sp = self.selfdrive.events_sp
- self.disengage_on_accelerator = not self.CP.alternativeExperience & ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
-
+ self.disengage_on_accelerator = Params().get_bool("DisengageOnAccelerator")
if self.CP.brand == "hyundai":
if self.CP.flags & (HyundaiFlags.HAS_LDA_BUTTON | HyundaiFlags.CANFD):
self.allow_always = True
diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py
index 27fac08673..02ae52d77e 100644
--- a/sunnypilot/models/fetcher.py
+++ b/sunnypilot/models/fetcher.py
@@ -115,7 +115,7 @@ class ModelCache:
class ModelFetcher:
"""Handles fetching and caching of model data from remote source"""
- MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v3.json"
+ MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v4.json"
def __init__(self, params: Params):
self.params = params
diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py
index 344a5179c9..87f00ea5fa 100644
--- a/sunnypilot/models/helpers.py
+++ b/sunnypilot/models/helpers.py
@@ -19,8 +19,8 @@ from openpilot.system.hardware import PC
from openpilot.system.hardware.hw import Paths
from pathlib import Path
-CURRENT_SELECTOR_VERSION = 4
-REQUIRED_MIN_SELECTOR_VERSION = 2
+CURRENT_SELECTOR_VERSION = 5
+REQUIRED_MIN_SELECTOR_VERSION = 5
USE_ONNX = os.getenv('USE_ONNX', PC)
diff --git a/sunnypilot/models/tests/model_hash b/sunnypilot/models/tests/model_hash
index 0042d08320..d51753107e 100644
--- a/sunnypilot/models/tests/model_hash
+++ b/sunnypilot/models/tests/model_hash
@@ -1 +1 @@
-8ef2dbcae743eb132167074a374f0a834308be31cffd532598bb13c3d7144a57
\ No newline at end of file
+bfbbf61606dad5d9349db246ad51a3468490d756bc61106a63fb5a220af4cdc6
\ No newline at end of file
diff --git a/system/athena/athenad.py b/system/athena/athenad.py
index 4292c23509..018fa0a0b5 100755
--- a/system/athena/athenad.py
+++ b/system/athena/athenad.py
@@ -565,7 +565,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
diff --git a/system/hardware/base.py b/system/hardware/base.py
index 1e3b94e44e..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*
@@ -33,6 +46,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
@@ -50,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]:
@@ -104,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/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
diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py
old mode 100755
new mode 100644
index df76c1a5fd..f657966ddf
--- a/system/hardware/tici/esim.py
+++ b/system/hardware/tici/esim.py
@@ -1,115 +1,111 @@
-#!/usr/bin/env python3
+import json
import os
-import math
-import time
-import binascii
-import requests
-import serial
+import shutil
import subprocess
+from typing import Literal
+from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile
-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 TiciLPA(LPABase):
+ 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'
- 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
+ self.timeout_sec = 45
+ if shutil.which('lpac') is None:
+ raise LPAError('lpac not found, must be installed!')
-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 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 at(self, cmd):
- print(f"==> {cmd}")
- self.dev.write(cmd.encode() + b'\r\n')
+ def get_active_profile(self) -> Profile | None:
+ return next((p for p in self.list_profiles() if p.enabled), None)
- 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
+ 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:
+ raise LPAError('cannot delete active profile, switch to another profile first')
+ self._validate_successful(self._invoke('profile', 'delete', iccid))
+ self._process_notifications()
- def download_ota(self, qr):
- return self.at(f'AT+QESIM="ota","{qr}"')
+ 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 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 nickname_profile(self, iccid: str, nickname: str) -> None:
+ self._validate_profile_exists(iccid)
+ self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname))
- 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 switch_profile(self, iccid: str) -> None:
+ self._enable_profile(iccid)
- r = post(f"https://{smdp}/{url}", payload)
- to_send = binascii.hexlify(r).decode()
+ 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()
- 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 _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
- if '+QESIM:"download",1' in out:
- raise Exception("profile install failed")
- elif '+QESIM:"download",0' in out:
- print("done, successfully loaded")
- break
+ messages = []
+ for line in out.decode().strip().splitlines():
+ if line.startswith('{'):
+ message = json.loads(line)
- def enable(self, iccid):
- self.at(f'AT+QESIM="enable","{iccid}"')
+ # 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'
- def disable(self, iccid):
- self.at(f'AT+QESIM="disable","{iccid}"')
+ 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']}")
- def delete(self, iccid):
- self.at(f'AT+QESIM="delete","{iccid}"')
+ messages.append(message)
- def list_profiles(self):
- out = self.at('AT+QESIM="list"')
- return out.strip().splitlines()[1:]
+ if len(messages) == 0:
+ raise LPAError(f"lpac {cmd} returned no messages")
+ return messages
-if __name__ == "__main__":
- import sys
+ 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'))
- 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)
+ 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')
- lpa = LPA()
- print(lpa.list_profiles())
- if len(sys.argv) > 1:
- lpa.download(sys.argv[1])
- print(lpa.list_profiles())
+ def _validate_successful(self, msgs: list[dict]) -> None:
+ assert msgs[-1]['payload']['message'] == 'success', 'expected success notification'
diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py
index ffa852403f..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 ""
@@ -297,13 +301,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 []
@@ -335,6 +337,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,
diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py
new file mode 100644
index 0000000000..6fab931cce
--- /dev/null
+++ b/system/hardware/tici/tests/test_esim.py
@@ -0,0 +1,51 @@
+import pytest
+
+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
+TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5'
+TEST_ICCID = '8944476500001944011'
+
+TEST_NICKNAME = 'test_profile'
+
+def cleanup():
+ lpa = HARDWARE.get_sim_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 = HARDWARE.get_sim_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/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=[]),
]
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 eae5962277..37da413806 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
@@ -12,23 +12,28 @@ 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'
+SCALE = float(os.getenv("SCALE", "1.0"))
DEFAULT_TEXT_SIZE = 60
-DEFAULT_TEXT_COLOR = rl.Color(200, 200, 200, 255)
-FONT_DIR = os.path.join(BASEDIR, "selfdrive/assets/fonts")
+DEFAULT_TEXT_COLOR = rl.WHITE
+
+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:
@@ -36,10 +41,15 @@ class GuiApplication:
self._fonts: dict[FontWeight, rl.Font] = {}
self._width = width
self._height = height
- self._textures: list[rl.Texture] = []
+ 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()
self._window_close_requested = False
+ self._trace_log_callback = None
def request_close(self):
self._window_close_requested = True
@@ -50,57 +60,113 @@ class GuiApplication:
HARDWARE.set_display_power(True)
HARDWARE.set_screen_brightness(65)
- rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT | rl.ConfigFlags.FLAG_VSYNC_HINT)
- rl.init_window(self._width, self._height, title)
+ self._set_log_callback()
+ rl.set_trace_log_level(rl.TraceLogLevel.LOG_ALL)
+
+ 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._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_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR)
rl.set_target_fps(fps)
self._target_fps = fps
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]
+
+ 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
+
+ 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)
+ 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 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)
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):
- 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()):
+ 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
+ yield
- if DEBUG_FPS:
- rl.draw_fps(10, 10)
+ 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)
- rl.end_drawing()
- self._monitor_fps()
+ if DEBUG_FPS:
+ rl.draw_fps(10, 10)
- def font(self, font_weight: FontWeight=FontWeight.NORMAL):
+ rl.end_drawing()
+ self._monitor_fps()
+ except KeyboardInterrupt:
+ pass
+
+ def font(self, font_weight: FontWeight = FontWeight.NORMAL):
return self._fonts[font_weight]
@property
@@ -113,21 +179,34 @@ 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",
+ )
+
+ # Create a character set from our keyboard layouts
+ from openpilot.system.ui.widgets.keyboard import KEYBOARD_LAYOUTS
+ 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)
+
+ codepoint_count = rl.ffi.new("int *", 1)
+ codepoints = rl.load_codepoints(all_chars, codepoint_count)
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, 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):
@@ -137,6 +216,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.error(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()
diff --git a/system/ui/lib/button.py b/system/ui/lib/button.py
index 034189275f..9ca82c9732 100644
--- a/system/ui/lib/button.py
+++ b/system/ui/lib/button.py
@@ -8,11 +8,21 @@ 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):
+ LEFT = 0
+ CENTER = 1
+ 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)
+ACTION_BUTTON_FONT_SIZE = 48
+ACTION_BUTTON_TEXT_COLOR = rl.Color(0, 0, 0, 255)
BUTTON_BACKGROUND_COLORS = {
@@ -20,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 = {
@@ -27,8 +38,11 @@ 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),
}
+_pressed_buttons: set[str] = set() # Track mouse press state globally
+
def gui_button(
rect: rl.Rectangle,
@@ -38,16 +52,42 @@ 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
+ 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:
+ 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):
- 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)
+ is_pressed = button_id in _pressed_buttons
+
+ if mouse_over:
+ if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
+ # 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_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
+
+ # 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:
+ _pressed_buttons.remove(button_id)
# Draw the button with rounded corners
roundness = border_radius / (min(rect.width, rect.height) / 2)
@@ -57,15 +97,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)
- # 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
- # 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)
+ # 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
+
+ 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 = 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/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/lib/inputbox.py b/system/ui/lib/inputbox.py
new file mode 100644
index 0000000000..367e0dc7e4
--- /dev/null
+++ b/system/ui/lib/inputbox.py
@@ -0,0 +1,232 @@
+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:
+ 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 = 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):
+ 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)
+ self._update_text_offset()
+
+ def set_password_mode(self, password_mode):
+ self._password_mode = password_mode
+
+ 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."""
+ if 0 <= position <= len(self._input_text):
+ 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 = self._get_display_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."""
+ 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
+
+ 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.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)
+
+ # Draw input box
+ rl.draw_rectangle_rec(rect, 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 = self._get_display_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 - self._text_offset), 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
+
+ # 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.WHITE)
+
+ 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()
+ 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:
+ font = gui_app.font()
+ 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
+ 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)
+
+ def _handle_keyboard_input(self):
+ # 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)
+ 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))
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(
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/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py
index af533e9a2a..96726bd999 100644
--- a/system/ui/lib/wifi_manager.py
+++ b/system/ui/lib/wifi_manager.py
@@ -1,17 +1,27 @@
import asyncio
+import concurrent.futures
+import copy
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
from dbus_next.errors import DBusError
from dbus_next.constants import MessageType
+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")
+
# NetworkManager constants
NM = "org.freedesktop.NetworkManager"
NM_PATH = '/org/freedesktop/NetworkManager'
@@ -25,6 +35,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
@@ -48,14 +61,16 @@ class NetworkInfo:
security_type: SecurityType
path: str
bssid: str
+ is_saved: bool = False
# saved_path: str
@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
+ networks_updated: Callable[[list[NetworkInfo]], None] | None = None
class WifiManager:
@@ -68,7 +83,14 @@ class WifiManager:
self.saved_connections: dict[str, str] = {}
self.active_ap_path: str = ""
self.scan_task: asyncio.Task | None = None
+ # Set tethering ssid as "weedle" + first 4 characters of a dongle id
+ self._tethering_ssid = "weedle"
+ 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
async def connect(self) -> None:
"""Connect to the DBus system bus."""
@@ -79,6 +101,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:
@@ -97,7 +120,7 @@ class WifiManager:
except asyncio.CancelledError:
pass
if self.bus:
- await self.bus.disconnect()
+ self.bus.disconnect()
async def request_scan(self) -> None:
try:
@@ -123,6 +146,12 @@ 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
+
+ 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}")
@@ -143,6 +172,18 @@ 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
+
+ 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'),
@@ -172,8 +213,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:
@@ -195,6 +236,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:
@@ -239,11 +434,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."""
@@ -258,6 +464,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:
@@ -266,6 +474,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}")
@@ -323,6 +532,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:
@@ -339,6 +549,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:
@@ -383,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
@@ -426,22 +640,19 @@ 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():
self._thread.join(timeout=2.0)
self._running = False
- @property
- def networks(self) -> list[NetworkInfo]:
- """Get the current list of networks."""
- return self._manager.networks if self._manager else []
-
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 +690,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
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)
diff --git a/system/ui/setup.py b/system/ui/setup.py
new file mode 100755
index 0000000000..0ec5d6395e
--- /dev/null
+++ b/system/ui/setup.py
@@ -0,0 +1,349 @@
+#!/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(OPENPILOT_URL, 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):
+ 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)
+
+ 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,
+ is_enabled=continue_enabled,
+ ):
+ 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()
diff --git a/system/ui/spinner.py b/system/ui/spinner.py
index 119bdba3e7..2af24c4e51 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("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] = []
@@ -101,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()
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)
diff --git a/system/ui/updater.py b/system/ui/updater.py
index eb9d766a16..3ee02ce97c 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,17 @@ 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)
+ 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"):
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)
diff --git a/system/ui/widgets/cameraview.py b/system/ui/widgets/cameraview.py
new file mode 100644
index 0000000000..01aac0370b
--- /dev/null
+++ b/system/ui/widgets/cameraview.py
@@ -0,0 +1,204 @@
+import pyray as rl
+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 = """
+#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);
+}
+"""
+
+# 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
+
+ # 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)
+
+ def render(self, rect: rl.Rectangle):
+ if not self._ensure_connection():
+ return
+
+ # Try to get a new buffer without blocking
+ buffer = self.client.recv(timeout_ms=0)
+ if buffer:
+ self.frame = buffer
+
+ if not self.frame:
+ return
+
+ # 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)
+
+ # 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)
+ 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()
+
+ 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")
+ 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()
diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py
index e5ca002ecf..f42220053b 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,19 +27,14 @@ 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)
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 13fdb912b5..0e22f1b527 100644
--- a/system/ui/widgets/keyboard.py
+++ b/system/ui/widgets/keyboard.py
@@ -1,29 +1,40 @@
+import time
+from typing import Literal
import pyray as rl
-from openpilot.system.ui.lib.button import gui_button
+from openpilot.system.ui.lib.application import gui_app, FontWeight
+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
+DOUBLE_CLICK_THRESHOLD = 0.5 # seconds
+DELETE_REPEAT_DELAY = 0.5
+DELETE_REPEAT_INTERVAL = 0.07
+
# Constants for special keys
+CONTENT_MARGIN = 50
BACKSPACE_KEY = "<-"
-ENTER_KEY = "Enter"
+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": [
@@ -42,37 +53,79 @@ keyboard_layouts = {
class Keyboard:
- def __init__(self, max_text_size: int = 255):
- self._layout = keyboard_layouts["lowercase"]
+ def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False):
+ 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._string_pointer = rl.ffi.new("char[]", max_text_size)
- self._input_text = ""
- self._clear()
+ 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
+
+ # 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 = {
+ BACKSPACE_KEY: gui_app.texture("icons/backspace.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),
+ }
@property
def text(self):
- result = rl.ffi.string(self._string_pointer).decode("utf-8")
- self._clear()
- return result
+ return self._input_box.text
- def render(self, rect, title, sub_title):
- 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()
+ def clear(self):
+ 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)
+ 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._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")
+ # 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)
+
+ # 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
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)
@@ -84,7 +137,28 @@ class Keyboard:
key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height)
start_x += new_width
- if gui_button(key_rect, key):
+ 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
+ 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, KEY_FONT_SIZE, is_enabled=is_enabled)
+
+ if result:
if key == ENTER_KEY:
return 1
else:
@@ -92,27 +166,68 @@ 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"]
- 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"]
- 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
+ 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"
- 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
+if __name__ == "__main__":
+ gui_app.init_window("Keyboard")
+ keyboard = Keyboard(min_text_size=8, show_password_toggle=True)
+ 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()
diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py
index 59427b57b8..5877b3ff7a 100644
--- a/system/ui/widgets/network.py
+++ b/system/ui/widgets/network.py
@@ -2,17 +2,26 @@ 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.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
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
+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:
@@ -46,28 +55,34 @@ 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, show_password_toggle=True)
+
+ 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
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)
+ password = self.keyboard.text
+ self.keyboard.clear()
+
+ if len(password) >= MIN_PASSWORD_LENGTH:
+ self.connect_to_network(network, password)
elif result == 0:
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:
@@ -76,34 +91,39 @@ 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.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)
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:
@@ -111,41 +131,76 @@ 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)
+ 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:
+ 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", button_style=ButtonStyle.ACTION) and clicked:
+ self.state = StateShowForgetConfirm(network)
- # If the network is saved, show the "Forget" button
- if self.wifi_manager.is_saved(network.ssid):
- 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 not self.wifi_manager.is_saved(network.ssid):
+ if isinstance(self.state, StateIdle) and rl.check_collision_point_rec(rl.get_mouse_position(), ssid_rect) and clicked:
+ if not network.is_saved and network.security_type != SecurityType.OPEN:
self.state = StateNeedsAuth(network)
- else:
+ 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 = None
+ if network.is_connected:
+ icon_file = "icons/checkmark.png"
+ elif network.security_type == SecurityType.UNSUPPORTED:
+ icon_file = "icons/circled_slash.png"
+ 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)
+
+ 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 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_need_auth(self):
+ def _on_network_updated(self, networks: list[NetworkInfo]):
+ self._networks = networks
+
+ 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):
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
diff --git a/tinygrad_repo b/tinygrad_repo
index 0e34f9082e..519dec6677 160000
--- a/tinygrad_repo
+++ b/tinygrad_repo
@@ -1 +1 @@
-Subproject commit 0e34f9082e9730b5df9c055b094a43e4565e413b
+Subproject commit 519dec6677f98718ee4f2d07be1936eb91dde73b
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;
diff --git a/tools/clip/run.py b/tools/clip/run.py
index 33c98a0213..a84290f9c4 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
@@ -15,8 +16,10 @@ from typing import Literal
from cereal.messaging import SubMaster
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
DEFAULT_OUTPUT = 'output.mp4'
DEMO_START = 90
@@ -28,6 +31,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())
@@ -60,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()}",
])
@@ -112,6 +121,22 @@ def parse_args(parser: ArgumentParser):
return args
+def populate_car_params(lr: LogReader):
+ 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
+ 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]):
return Popen(args, env=env, stdout=PIPE, stderr=PIPE)
@@ -155,8 +180,19 @@ 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')
+ lr = get_logreader(route)
begin_at = max(start - SECONDS_TO_WARM, 0)
duration = end - start
@@ -165,20 +201,40 @@ 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)}'
- meta_text = get_meta_text(route)
+ box_style = 'box=1:boxcolor=black@0.33:boxborderw=7'
+ meta_text = get_meta_text(lr, 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={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={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=1:boxcolor=black@0.33:boxborderw=10: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', '-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]
+ 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':
@@ -189,6 +245,8 @@ def clip(data_dir: str | None, quality: Literal['low', 'high'], prefix: str, rou
xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}']
with OpenpilotPrefix(prefix, shared_download_cache=True):
+ populate_car_params(lr)
+
env = os.environ.copy()
env['DISPLAY'] = display
@@ -234,14 +292,27 @@ 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(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,
+ )
+ 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__':
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 \
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():
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
diff --git a/tools/lib/route.py b/tools/lib/route.py
index 831a1e2ee5..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,15 +20,22 @@ ECAMERA_FILENAMES = ['ecamera.hevc']
class Route:
def __init__(self, name, data_dir=None):
+ self._metadata = 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:
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
@@ -60,12 +68,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())
@@ -85,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(
@@ -95,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)
@@ -160,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}')
@@ -168,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
@@ -181,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):
diff --git a/tools/op.sh b/tools/op.sh
index 93e774a2ac..9142d50a18 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() {
@@ -293,6 +293,11 @@ function op_check() {
unset VERBOSE
}
+function op_esim() {
+ op_before_cmd
+ op_run_command system/hardware/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 "$@" ;;
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")
diff --git a/uv.lock b/uv.lock
index fbeda4d552..2f8cf68094 100644
--- a/uv.lock
+++ b/uv.lock
@@ -163,7 +163,7 @@ wheels = [
[[package]]
name = "azure-identity"
-version = "1.21.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/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/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/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/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]]
@@ -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.1"
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/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/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/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]]
@@ -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" },
@@ -1099,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]]
@@ -1213,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]]
@@ -1238,7 +1245,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 +1457,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 +1470,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,20 +1535,20 @@ 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]]
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]]
@@ -1605,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]]
@@ -1733,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]]
@@ -1862,162 +1870,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 +2047,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 +2062,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 +2076,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 +2090,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 +2104,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 +2118,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 +2132,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 +2146,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 +2162,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 +2176,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 +2190,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 +2204,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 +2218,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 +2232,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 +2249,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 +2264,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 +2278,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 +2292,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 +2309,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 +2323,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 +2337,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 +2351,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 +2365,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 +2379,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 +2396,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 +2410,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 +2427,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 +2440,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 +2454,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 +2468,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 +2482,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 +2497,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 +2511,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 +2526,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 +2540,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 +2554,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 +2568,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 +2582,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 +2596,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 +2610,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 +2624,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 +2638,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 +2652,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 +2667,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 +2681,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 +2696,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 +2710,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 +2724,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 +2738,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 +2752,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 +2766,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 +2780,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 +2794,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 +2809,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 +2823,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 +2837,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 +2851,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 +2865,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 +2879,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 +2893,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 +2907,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 +2921,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 +2935,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 +2949,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 +2963,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 +2977,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 +2991,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 +3006,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 +3021,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 +3035,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 +3049,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 +3063,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 +3077,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 +3092,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 +3106,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 +3120,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 +3134,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 +3148,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 +3162,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 +3176,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 +3190,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 +3204,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 +3218,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 +3232,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 +3246,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 +3261,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 +3276,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 +3291,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 +3305,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 +3321,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 +3335,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 +3351,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 +3366,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 +3380,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 +3394,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 +3408,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 +3422,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 +3437,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 +3451,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 +3465,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 +3479,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 +3493,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 +3508,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 +3522,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 +3536,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 +3550,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 +3564,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 +3578,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 +3592,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 +3606,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 +3620,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 +3636,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 +3650,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 +3664,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 +3678,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 +3692,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 +3706,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 +3720,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 +3734,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 +3748,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 +3763,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 +3777,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 +3791,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 +3806,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 +3821,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 +3836,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 +3850,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 +3864,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 +3878,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 +3892,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 +3906,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 +3921,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 +3936,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 +3951,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 +3965,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 +3979,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 +3993,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 +4007,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 +4021,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 +4035,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 +4049,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 +4064,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 +4078,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 +4092,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 +4107,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 +4121,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 +4135,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 +4149,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 +4163,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 +4177,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 +4192,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 +4206,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 +4222,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 +4236,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 +4252,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 +4266,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 +4444,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 +4484,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 +4502,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 +4590,14 @@ wheels = [
[[package]]
name = "pyyaml-env-tag"
-version = "0.1"
+version = "1.1"
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/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/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/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]]
@@ -4671,7 +4679,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 +4689,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 +4745,27 @@ wheels = [
[[package]]
name = "ruff"
-version = "0.11.8"
+version = "0.11.10"
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/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/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/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]]
@@ -4771,15 +4779,15 @@ wheels = [
[[package]]
name = "sentry-sdk"
-version = "2.27.0"
+version = "2.29.1"
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/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/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/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]]
@@ -4816,38 +4824,38 @@ wheels = [
[[package]]
name = "setuptools"
-version = "80.3.1"
+version = "80.8.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/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/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/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]]
name = "shapely"
-version = "2.1.0"
+version = "2.1.1"
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" }
+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]]
@@ -4899,24 +4907,24 @@ 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]]
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"
@@ -4982,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]]
@@ -5086,7 +5094,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 = [